AI 资讯
Juinper Networks
Upgrading Juniper MX Networks from 100GbE to 400GbE: What Engineers Need to Know Moving a production network from 100 Gigabit Ethernet to 400 Gigabit Ethernet sounds simple on paper: Replace a 100G interface with a 400G interface and get four times the bandwidth. In a real carrier or data-center network, however, the interface is only one part of the equation. The router's forwarding silicon, switch fabric, midplane, power system, cooling, optics, software release, slot selection, redundancy configuration, and licensing can all determine whether the expected capacity is actually available. Juniper's MX240, MX480, and MX960 platforms provide an interesting example because these systems can be upgraded with newer generations of Modular Port Concentrators rather than requiring an immediate chassis replacement. One particularly useful case study is the Juniper MPC10E-15C , a Trio 5-based line card capable of supporting both 100GbE and 400GbE interfaces. This article isn't about whether you should buy a particular line card. Instead, we'll use the MPC10E-15C to examine the engineering questions that should be answered before attempting a 100G-to-400G upgrade on an existing Juniper MX network. Video Overview The video provides a short overview of the hardware. Below, we'll go deeper into the architecture and the deployment considerations that matter when integrating this class of line card into an existing MX environment. Why Moving from 100G to 400G Isn't Just a Port Upgrade Suppose an edge router has four heavily utilized 100GbE connections. At first glance, replacing those links with 400GbE interfaces appears straightforward. But consider what happens behind the physical port. Traffic entering that 400G interface must travel through several parts of the system: Interface → Packet Forwarding Engine → Fabric → Other line cards/interfaces Every component in that path needs sufficient capacity. A 400GbE optic connected to a router that cannot move 400 Gbps through its inte
AI 资讯
CQRS: Read-Write Separation Design Pattern
In traditional software architectures, we almost instinctively reach for the CRUD (Create, Read, Update, Delete) paradigm. We design an entity model, map it to a relational schema using an ORM, and use that identical abstraction to both alter state and display data on user dashboards. For simple applications, this works flawlessly. But as systems scale—both in business complexity and throughput, this dual-purpose model starts showing fractures: Write logic demands tight validation, transactional boundaries, normalization, and domain invariants. Read logic demands flat, pre-aggregated, denormalized representations across dozens of tables to serve responsive UIs. Trying to satisfy both masters with a single schema leads to unwieldy SQL joins, lock contention, compromised domain boundaries, and performance gridlock. This is where Command Query Responsibility Segregation (CQRS) enters the picture. 1. What is CQRS? Coined by Greg Young and based on Bertrand Meyer’s Command-Query Separation (CQS) principle, CQRS states that an application should use separate models to update and read data . At its philosophical core: Command (Write): Represents an intent to alter domain state (e.g., SubmitOrder , DeactivateUser , ChangeBillingAddress ). A command should focus entirely on domain logic, data integrity, and business rules. In strict CQRS, commands do not return domain data — only an acknowledgment, validation failure, or generated entity ID. Query (Read): Retrieves data without mutating application state (e.g., GetOrderSummaryById , ListCustomerInvoices ). Queries should execute side-effect-free operations that return lightweight Data Transfer Objects (DTOs). ┌────────────────────────────────────────────────────────┐ │ Client │ └─────────────┬────────────────────────────▲─────────────┘ │ │ Execute Command Run Query │ │ ▼ │ ┌───────────────────────────┐ ┌───────────┴─────────────┐ │ Command Model │ │ Query Model │ │ (Validation & Invariants) │ │ (Optimized for DTOs) │ └──────
AI 资讯
What is harness engineering and why should I care?
How do you ship a software product with 0 lines of manually-written code? A friend asked me this today, and I realized I didn't have a simple answer. So I dug deeper. It turns out the answer is in how you engineer your harness. Wait now, what? What is harness engineering? There is a reason this is the most important trend right now around coding agents. The biggest question these days is how to validate AI-generated code without reading every single line. How do you make sure an agent doesn't break production or delete your data? A blog by OpenAI shared an interesting experiment where a team of 3 engineers have built and shipped an internal beta of a software product with 0 lines of manually-written code. Every line of code: application logic, tests, CI configuration, documentation, observability, and internal tooling, has been written by Codex. How did they do it? They didn't write the app. They designed the harness. What exactly is a harness? Think of an AI agent like a powerful racehorse. The harness is the track, the blinders, and the jockey's reins that keep it running in the right direction instead of jumping into the stands. As my colleague Arthur Thompson explained today: for agents — the harness is composed of all the deterministic components that wrap the LLM. Balaji Subramaniam details those deterministic components in his blog — the orchestration layer, execution sandboxing, state persistence, and verification tools. If you want to build reliable agentic systems, your job shifts from writing the logic to designing the environment. Here is what you need to focus on: Set strict boundaries: Don't let the agent guess what it can touch. Enforce strict access rules (like confining it to a specific sandbox) so it can't accidentally wipe out production data. Build "Repair Loops": Agents will inevitably make mistakes. A great harness automatically traps errors, like a failed build or a test failure, and feeds those clean logs right back to the agent so it can fix
AI 资讯
Can You Do iOS Development Without Xcode? A Full-Process Comparison from Environment Setup to Running on a Real Device
I had been using Xcode for iOS development until one day I changed computers. Downloading Xcode took nearly two hours, and after unzipping, I found only 20GB left on the hard drive. Every major version update involved over ten gigabytes of downloads, plus Simulator and various iOS SDKs, so a 256GB Mac soon required cleaning up space. Later, a new teammate arrived with a Windows laptop and wanted to write iOS code, but the Mac configuration hadn't been approved yet. The threshold of iOS development being tied to Mac and Xcode is indeed not flexible for many scenarios. So I began to wonder: can iOS development be done without Xcode? Are there lighter alternatives? Several Alternative Paths Without Installing Xcode I first tried the approach of VS Code plus remote Mac compilation. Write code on Windows, connect to a remote Mac via SSH, and execute xcodebuild. The coding environment problem was solved, but the debugging phase is unavoidable—running on a real device requires Xcode to handle provisioning profiles and signing, so ultimately a Mac with full Xcode is still needed. Moreover, after each code change, the three steps of local editing, remote compilation, and syncing to the device made the workflow longer than developing directly in Xcode. I also tried the CI approach. Codemagic and GitHub Actions can automate build packaging, suitable for continuous integration before releases. However, frequent debugging and modifications during daily development—changing a line of code and running to see the result—cannot be pushed to CI every time and wait a few minutes. Its coverage is limited. I also considered AppCode, but it essentially still depends on Xcode's toolchain, and JetBrains has discontinued its maintenance. Another Approach: KXApp IDE KXApp has built the compilation toolchain into the IDE, allowing iOS applications to be compiled and signed without installing Xcode on the system. It uses VS Code as its editor layer, with shortcuts, interface layout, and plugin
AI 资讯
Common Reasons for App Store Review Rejection: Screenshot Specs and Permission Description Pitfalls
Anyone who does iOS development has likely encountered this situation: after submitting your app with great effort, you receive an App Store review rejection email the next day with a variety of reasons—incorrect screenshot sizes, missing privacy policy links, unclear permission usage descriptions, and so on. Most rejection reasons can be avoided in advance, without waiting to be rejected before making changes. Here are the common reasons for rejection, categorized and explained with how to fix each. Screenshots and Metadata Issues Screenshots are a frequent cause of rejection. Rejections can result from sizes not conforming to App Store specifications, screenshots containing unreleased features, screenshots marked with test environment indicators (such as "beta" watermarks), screenshots inconsistent with the actual interface, or screenshots with incorrect device model labels. These issues can be caught by checking for yourself before submission. How to fix: Regenerate screenshots at the sizes required by App Store, with one set for 6.5 inches and another for 5.5 inches. If your app supports iPhone only, these two sets are sufficient; if it supports iPad, add an additional set. App Store screenshots do not support transparent backgrounds (alpha channel), so convert transparent backgrounds to opaque before uploading. When batch-uploading screenshots, tools like Appuploader allow you to select multiple images at once and upload them automatically sorted by device size, rather than dragging them one by one on the web page. Privacy Issues Missing privacy policy links or incomplete content is a common reason for rejection. If your app collects user data (including via third-party SDKs), you must provide a privacy policy, and the link must be accessible. How to fix: Prepare a privacy policy page and host it at an accessible URL, then fill in the link in the privacy policy section in App Store Connect. If your app involves third-party SDKs for advertising, analytics, payme
AI 资讯
Test-Post: Review-Queue UI
Warum KI-Agenten Leitplanken brauchen: Operatives Gedächtnis statt Over-Engineering Ki-Agenten sind nicht böse. Sie sind nicht einmal unzuverlässig im klassischen Sinne. Das eigentliche Problem ist vielmehr ihre beständige Bereitschaft zu helfen, gepaart mit einem fehlenden Verständnis für die Grenzen ihrer Befugnisse. Sie wollen das Problem lösen, das ihnen gestellt wird, oft mit einer Aggressivität, die menschliche Manager selten aufbringen. Wenn ein Agent eine Produktionsdatenbank bereinigen soll, tut er es. Wenn er eine Datei löschen soll, die er für überflüssig hält, weil sie im aktuellen Kontext nicht erwähnt wurde, wird er es tun. Wir haben in unserem Engineering-Team 182 sogenannte Guards implementiert. Diese Zahl klingt auf den ersten Blick nach extremem Over-Engineering. Nach 182 Prüfungsschritten, die vor jeder Aktion eines autonomen Agents laufen, könnte man meinen, wir hätten ein unverhältnismäßig komplexes System gebaut. Doch jeder einzelne dieser Guards entstand nicht aus theoretischer Vorsicht. Jeder einzelne steckt in einem echten Vorfall, bei dem ein Agent ohne diese Barriere etwas getan hätte, das wir nicht rückgängig machen konnten oder das immense Kosten verursacht hätte. Dies ist kein Over-Engineering. Das ist operatives Gedächtnis. Was ist ein Guard? Ein Guard ist eine schlanke, deterministische Prüflogik, die zwischen der Entscheidungsfindung der KI und der tatsächlichen Ausführung einer Aktion liegt. Die KI plant eine Aktion. Zum Beispiel: "Führe einen SQL-Update-Befehl auf der Tabelle 'users' aus." Bevor dieser Befehl an die Datenbank geschickt wird, läuft er durch eine Pipeline aus Guards. Ein Guard fragt nicht nach dem "Warum" der KI. Das ist die Domäne des Large Language Models. Der Guard fragt nach den "Was" und "Wie" der realen Welt. Er prüft Fakten, nicht Absichten. Ein typischer Guard könnte so aussehen: def check_write_scope ( agent_action : dict ) -> bool : """ Stellt sicher, dass Schreiboperationen nur auf spezifisch erlaubten Tab
AI 资讯
The Real Cost of Context Switching: What Security Alerts Actually Do to Developer Flow
developer context switching security DevSecOps flow state developer velocity security alerts batch security patching ROI cost of context switching developer productivity security security alert fatigue developer cognitive load ad-hoc security patching interrupting developer flow engineering vp productivity metrics DevSecOps velocity context switching recovery time 23 minute recovery context switch batching security alerts SLA-backed fix campaigns security SLA for developers minimizing context switching feature delivery vs security developer experience DevSecOps The Real Cost of Context Switching What Security Alerts Actually Do to Developer Flow Back to blog What interruptions actually cost Is it worse for developers specifically? The research says probably yes The alert volume isn't imaginary — but be careful which numbers you cite The fix: batch the routine work, protect the calendar The important exception: not everything can wait for the batch A more honest way to estimate the ROI The takeaway Sources The Real Cost of Context Switching: What Security Alerts Actually Do to Developer Flow Companies keep investing in better frameworks, tighter deployment gates, and broader platform suites — and feature delivery keeps getting slower anyway. For engineering leaders trying to explain that paradox to the board, the usual suspects (headcount, tooling, talent) rarely hold up. The more useful place to look is something less visible: how often developers get pulled out of what they're doing, and what it costs them to get back in. As "shift-left" security practices spread, developers absorb a steady stream of vulnerability alerts, automated pull-request comments, and one-off Jira tickets throughout the day. The goal — a more secure codebase — is the right one. The delivery mechanism is often the problem. Scattering fixes across random moments in the workday erodes productivity without necessarily making the codebase safer any faster. The alternative a growing number of engi
AI 资讯
New Here — Figured I’d Introduce Myself and DaemonCore
What’s up everyone. My name is Theodore Ochsen, founder of DaemonCore. I’ve been a developer for well over a decade, and my background goes back to running three PC repair shops before eventually moving heavily into software development, cybersecurity and building applications with my team. My road here has been anything but normal. A serious auto accident in 2015 basically nuked the life I had built. I lost the shops, lost my ability to walk and spent almost three years in rehab learning to walk again. Eventually I lost my marriage, my home and ended up in a wheelchair with pretty much one thing I could still do: code. I used to park myself at Barnes & Noble and read programming books for hours because I couldnt afford to buy all the damn things. Then I'd go back and try what I learned. I just kept building. In 2022 I went back to school for Forensic Psychology with a focus on cybercrime, and I graduate with my BA next month. At the same time, DaemonCore has grown into a team building software, security tools and some weird shit that occasionally starts as “what if we tried this?” and somehow becomes an actual product. One of our newest projects is DaemonCore Academy, which we started building last September and finally launched publicly about a week ago. The concept is pretty simple: cybersecurity education should be hands-on and the knowledge should be free. No three lessons followed by a credit-card screen. We start with fundamentals and work toward hands-on drills and environments where people can actually experiment, break things legally, understand WHY they broke, reset and try again. I'm joining DEV because I dont just want to drop links and disappear. I want to talk development. Architecture, Android, React, databases, security, stupid bugs that steal six hours of your life, things we've learned the hard way, and probably a few things we'll get completely fucking wrong and learn from publicly. I’ve spent enough years doing this to know one thing for certain
AI 资讯
A Check That Has Never Run Is Not Passing
Originally published on hexisteme notes . I run a video pipeline where every shot carries a contract: a must_have list, a must_not_have list, and a serves_line field — the narration line the shot exists to support. One rule in that pipeline, which I call the enactment rule, checks each contract for self-contradiction: if must_not_have forbids the very thing serves_line requires, the shot can't honor its own contract and support its sentence at the same time, so the rule fails it hard at author time. It's a good rule to want. It had never once run. One layer deeper than the usual dead gate This adds another entry to the same line of investigation: Grep won't find your dead gates. A fill-rate query will. found rules nobody was calling — the wiring itself was absent; The Guard Passed on an Empty Table found a correctly-called rule whose target population was empty at runtime, so it passed on nothing left to check; and The check that cannot fire found a rule that ran against real data and silently disabled itself because its threshold was an absolute constant that didn't match the input's scale. This one sits a layer deeper than all three: the callers existed, the rule fired on every invocation, and what was actually missing was a producer for the one field the rule needed to read. The field nothing writes serves_line is a derived field — by definition, it's the narration of the beat the shot is attached to. I checked that derivation against reality before touching anything else: every stored value that existed matched its beat's narration exactly, 6 of 6, zero drift. Wherever the field existed, it was right. But almost nowhere did it exist. A repo-wide search for anything that writes serves_line returned zero producers. The archetype templates that generate contracts don't set it. So every contract created through the supported authoring path had serves_line = None , and the rule — which needs that field as its input — silently evaluated nothing. A published episode ha
AI 资讯
Title: My CI Caught a Bug My Local Environment Never Would
Spent the past week wiring up CI and tightening a few decisions on a backend project. Nothing dramatic happened, but a few things stood out enough to write down. CI is worth setting up early, even on a solo project. First run caught a dependency that worked locally but was never actually declared in requirements.txt . Classic "works on my machine" gap. CI doesn't care what your machine has installed, only what the project actually declares, and that mismatch is exactly the kind of thing that's invisible until something forces the comparison. Pinned dependencies drift more easily than people expect. I had a specific package version pinned for a known compatibility issue, and a later, unrelated install silently bumped it past that pin. Caught it by chance during a review, not because anything alerted me. Worth adding an explicit check for that instead of relying on remembering. 404 over 403 for resources that belong to another user is a small choice with real weight. 403 confirms something exists and you're just not allowed to see it. 404 gives nothing away. Costs a bit of clarity for legitimate callers debugging their own mistakes, but that's a fair trade for not leaking what exists in the system. None of this is complicated. All of it is easy to miss quietly, and only shows up if something is actually checking. That's most of what good backend hygiene turns out to be.
AI 资讯
Google’s Android update tackles motion sickness, accessibility, and more
While some of the features see Google playing catch-up to Apple, which already offers similar features for iPhone users, others specifically leverage Gemini to provide various improvements.
创业投融资
John Ternus hypes ‘huge launch next week’ in first memo as Apple CEO
Apple is hosting its iPhone release event next week, which is rumored to feature the first-ever foldable iPhone.
AI 资讯
Who is John Ternus, the new Apple CEO?
Starting on September 1, Ternus will lead one of the world's most valuable companies, but if you're not a dedicated Apple enthusiast, you've probably never heard of this man, who has largely remained out of the spotlight until now.
开发者
Zstandard einfach erklärt in 2 Episoden — Episode 1
Episode 1: Was in einer ZST-Datei passiertEpisode 1: Was in einer ZST-Datei passiertZST-Dateien begegnen uns immer häufiger bei großen Downloads, Softwarepaketen, Backups und Serverdaten. Sie sind oft deutlich kleiner als die ursprünglichen Dateien und lassen sich trotzdem sehr schnell wieder entpacken. Doch wie funktioniert das? Warum werden Dateien komprimiert? Eine Datei besteht aus Daten. Je mehr Daten sie enthält, desto mehr Speicherplatz wird benötigt und desto länger dauert ihre Übertragung. Kompression versucht, dieselben Informationen mit weniger Daten darzustellen. Beim späteren Entpacken muss daraus wieder exakt die ursprüngliche Datei entstehen. Nach dem Entpacken ist die Datei Bit für Bit identisch mit dem Original. Es wird nichts weggelassen und nichts vereinfacht. Wiederholungen benötigen unnötig viel Platz Betrachten wir diesen Satz: Kleine Katzen kuscheln auf kleinen Kissen, junge Katzen kuscheln auf bunten Kissen und alte Katzen kuscheln auf weichen Kissen.Die folgenden Teile kommen mehrfach vor: A = Katzen kuscheln auf B = KissenWenn wir die wiederkehrenden Textteile durch die Variablen A und B ersetzen, können wir den Satz kürzer darstellen: Kleine A kleinen B, junge A bunten B und alte A weichen B.Damit ist der Text noch nicht vollständig. Zusätzlich müssen wir speichern, wofür A und B stehen: A = Katzen kuscheln auf B = KissenAus diesen Informationen lässt sich der ursprüngliche Satz wiederherstellen. Jedes A wird durch Katzen kuscheln auf und jedes B durch Kissen ersetzt. Das ist bereits die grundlegende Idee der verlustfreien Kompression: Wiederkehrende Daten werden nicht jedes Mal vollständig gespeichert. Stattdessen werden sie einmal gespeichert und anschließend durch kürzere Verweise ersetzt. ### Zstandard verwendet keine Variablen Unsere Variablen A und B dienen nur dazu, das Prinzip verständlich zu machen. Zstandard versteht weder Wörter noch Sätze. Es weiß nicht, was Katzen oder Kissen sind. Für das Programm besteht eine Datei lediglich
AI 资讯
Every Scan is A Write
What building a warehouse management system taught me about the data operational software leaves behind — and the engineering it takes to make that data trustworthy. The second that outlives itself A picker holds a handheld scanner, points it at a carton, and pulls the trigger. There's a beep. They type 10, confirm, and move to the next location. The whole thing takes about a second. For a long time I thought of my job as making that second work. I built the screen, the endpoint behind it, the repository behind that. My definition of done was that the user completed the workflow, the API returned success, and the right rows landed in the database. What changed my thinking was noticing what was still there afterwards. The screen closes, the session ends, the app ships a new version, the picker changes jobs, the device is replaced. The row stays — and the row isn't a record of a UI interaction. It's a durable claim about the physical world: at this time, this person, on this device, ten units of this product moved. The application is the instrument. The data is the measurement. A measurement is only ever worth what the instrument's precision allows. This article is about the gap between those two definitions of done, and the specific decisions — retry semantics, timestamps, identity, status codes, conflict resolution — that determine which side of it you land on. Almost all of them get made by application developers, inside feature work, long before anyone tries to analyze anything. What warehouse owners actually do with this data now Worth being concrete about the stakes first, because "data quality matters" is the kind of statement everyone agrees with and nobody acts on. What's changed isn't that owners suddenly became analytical. It's that operational systems started producing enough granular, attributed, time-stamped movement data that previously unanswerable questions became answerable. Inventory accuracy is a working-capital decision. Stock you can't trust is s
AI 资讯
The bug only showed up once the feature started working
Falsifier first: if you can find a fourth production call site that builds a Transformation and reconstructs its target field differently from the three I'm about to describe, this post is wrong about "all of them." I counted by grepping for the one function that computes a transformation's identity and checking every call site by hand. Three. If there's a fourth, the bug I'm describing isn't fully fixed. Here's the shape of it. Engine::plan_shape has a doc comment that says, more or less, "this isn't a second place where transformation identity gets defined, because it's the same code as the one true place." That claim was false, and it had been false since the field it's talking about was added. The actual second place was Engine::rehydrate_committed . Its job is to rebuild a Transformation from the journal when a fresh CLI process needs to undo something a previous process committed. Every gx undo call from a cold process goes through it. And for one field, target , it wasn't rebuilding anything. It wrote a hardcoded placeholder. Nobody noticed, because nothing disagreed with the placeholder. Every adapter shipping at the time also produced the placeholder for that field, by omission rather than by design, so the two sides matched by coincidence. A missing value that's always missing on both sides of a comparison is invisible. cargo check doesn't catch it because the type is Option<T> and None is a completely legal value of that type. Nothing was wrong, until something else became right. What made it right was landing the two adapters that finally do predict target , fs and git, so their production plan() calls started filling in the real value instead of leaving it empty. The moment that shipped, cold-process undo broke for every fs or git transformation: gx_code=INTERNAL detail="TransformationId(...) is Committed, and 43 §3 has no `rehydrate: the rebuilt transformation names another id, so the intent supplied is not the one this transformation was planned from`
AI 资讯
What happens to technical debt when AI makes code cheap?
Dear past Jenna, I know you're used to dealing with large, complex, legacy codebases riddled with...
AI 资讯
Badger: An E-Ink Badge I Use For Conferences
I attend conferences regularly, and for years I’ve wanted a badge that makes it easy for people to find me online. In 2019, I attended defcon and built defpi , a goofy raspberry pi powered badge. While that was fun I wanted a bit more turnkey and I found just what I wanted with a Badgeware Badger (nerdy domain hack badgewa.re). Since my use case is to just have my socials via QR I chose the e-ink version as it made the most sense. So what does the Badger come equipped with? RP2350 WiFi 1000mAh battery USB-C MicroPython I might not be the biggest Python fan but in the age of AI does it matter? I just ask Codex to help me get the desired outcome I'm looking for! So Codex and I cooked a few hundred lines of Python and boom my profiles are only a QR code scan away. Let's take a look at my default badge screen in all its glory. Social screen. All in all it's pretty simple, get you to either my LinkedIn or my personal site . The crazy thing is the simulator lets me test the code before I deploy it to the badge. make sim and I'm able to test it locally. Then a simple make deploy while the badge is connected and it deploys it all. You can check out the code repo if you wanna steal slopfork my setup. Hacksore / badger Badger 2350 badge Personal Badgeware app for the Badger 2350. Requirements: macOS, uv , Git, and a data-capable USB-C cable. Run make to see the three available commands. Simulator make sim The first run clones and builds Badgeware Desktop beside this repo. Later runs reuse that build, stage the current app in an isolated filesystem, and launch it at the Badger's 264×176 resolution. Simulator controls: Space : Button B; flip between the badge and social/QR screens ↑ / ↓ : change the background pattern ← / → : Buttons A and C Esc : hot reload Deploy the app Connect the Badger over USB-C. Double-tap RESET . Wait for the BADGER drive to appear. Run: make deploy The command validates the app, copies its Python and image files into /apps/badge , safely ejects the dr
AI 资讯
Tim Cook’s parting message: Apple is in the hands of a product builder
Tim Cook’s farewell memo to Apple employees offers a glimpse at how he wants John Ternus to be seen: as a product builder with deep experience across the iPhone, Mac, AirPods, and other major hardware lines.
开发者
DoorDash’s Flux Runs 130,000 Engineering Tasks Through Cloud-Based Agents
DoorDash has moved engineering agent workloads from developer laptops to its Flux cloud platform. The platform automated 130,000 engineering tasks in one month and supports more than 25,000 automated code reviews weekly. Flux uses isolated Firecracker microVMs, an MCP gateway, reusable playbooks, and multiple invocation surfaces to run agent workflows with scoped access and centralized auditing. By Leela Kumili