Indian Citizen
Electoral engine e1.12.1
This is the identification logic currently live on the electoral roll. Every report records the engine version that produced it, so a finding can always be read against the rules in force when it was made.
2026-09-01 · live
e1.12.1, Three ordinary mistyped URLs were hard 500s, and the state tiles now say how complete they are
- ⚠⚠ /voters/U05, /v/0/NOPE AND /open/NOPE ALL RETURNED CLOUDFLARE 1101. Each maps to a real internal prefix, so the path mapper claimed it and the electoral router then declined it, a state code is not a page, NOPE is not a reference, NOPE is too short to be a voter ID, and the null went straight out of fetch. A router that CLAIMS a path owns its failure too: it is a branded 404 now, and index.js will not return an empty response even if a future branch forgets. Found by an external QC pass, not by any gate here, because every gate drove paths that never reached that branch.
- ⚠ AND /voters/{state code} IS A GUESS AT A URL WORTH HONOURING. The explorer's real address is a query string; /voters/U05 is what a person types or a model invents, so it 301s to the page they meant. An unknown /api/ path answers JSON rather than an HTML 404, because whatever called it is a program.
- ⚠⚠ EVERY STATE TILE NOW CARRIES ITS COVERAGE AND ITS STATUS: the count, the percentage of that state's constituencies that hold voters in the index, a bar, and Updating below 98% or Loaded at or above it. THE PERCENTAGE COUNTS SEATS, NOT VOTERS, and the page says so: the register never publishes how many electors a state actually has, so a percentage of the electorate cannot be computed from anything here and any figure claiming to be one would be invented.
- ⚠⚠ THE HEADLINE SAID '36 of 36 states and UTs checked' WHILE FIVE TILES SAID THE STATE HELD NOTHING. That is the one line on the page that would make a reader believe coverage is national. It counts states with voters loaded now, and the sentence under it says how many are still to arrive.
- ⚠ THE MARK IS A THUMBPRINT IN THE BRAND BLUE, on a white tile. Four nested ridges and nothing else: a whorl is invisible below about 48px, and a print drawn accurately enough to be identifiable is a worse idea on a product about identifying people. The tile is what lets the blue survive a navy topbar and a light tab strip both.
- ⚠ TWO WAYS IN, NOT THREE: Complete Voter Analysis and Check any Voter. The hero search already covers searching a name, so a third card offering it was the page competing with itself.
- ⚠⚠ THE EXPLORER'S MAP AND FIGURES PANELS OVERLAPPED THE SELECTION BAND ON MOBILE. Both borrowed the shared panel-lift class for its shadow and inherited its margin-top:-26px, which exists so the FIRST card on a page rides over the navy hero. Measured at 390px: the map started 14 pixels inside the band and 18 above its own grid. The shadow is kept, the margin is not. The five pickers were also 27px tall against a 44px minimum tap target.
- ⚠ The home page's primary button shipped as a white ghost on a white card and did not read as tappable on a phone; it is a filled navy button matching the search page. The check form's relative placeholder was clipping at 390px and is now short with the explanation under the field. The report's action row wraps rather than shrinking below 44px.
- ⚠ A share card at /og.png, 1200x630, inlined in the bundle rather than served from an assets binding this Worker does not have, which is exactly why /og-cover.jpg on the court site has never worked. It must stay a raster: every social crawler that matters ignores SVG in og:image.
- ⚠ Report mail is CC'd to contact@indiancitizen.app, in the open rather than blind, so a recipient can see where to reply. Cloudflare Email Routing delivers that copy and CANNOT send the mail itself, it receives and forwards, and the Worker send_email binding only reaches verified routing destinations, never a requester's own inbox. The send stays on SES and the From address moves the day indiancitizen.app is verified there; the zone's SPF already includes amazonses.com.
- ⚠ Zone hardening, outside the code: Always Use HTTPS on, minimum TLS 1.2, HSTS started at a day, HTTP/3, Brotli, Early Hints, tiered cache, and browser cache set to respect the Worker's own per-part headers rather than override them.
- ⚠ THE BACKTICK-IN-A-TEMPLATE-LITERAL TRAP, NINTH TIME, in a comment explaining the panel-lift fix. Write no backticks in any comment that lives inside a template literal.
- Gates: citizen 309 (every assertion confirmed to fail on revert; three of them survived the first attempt and were rewritten, one compared a constant against itself and could never fail).
2026-09-01
e1.12.0, The electoral product has left eCourtsIndia and is now Indian Citizen, at indiancitizen.app
- ⚠⚠ A SEPARATE PRODUCT ON A SEPARATE DOMAIN, ON THE SAME WORKER. indiancitizen.app now serves the whole electoral product: the roll search, the roll explorer, the community map, the identification engine and every report. legalcheck.ecourtsindia.com/electoral/* 301s to it, report links included, so a link in a year-old email still opens the report. Same code, same D1, one deploy; src/index.js branches on the request hostname before anything else and the citizen router NEVER falls through to a LegalCheck route, so no path on the new domain can serve the old brand.
- ⚠ CLEAN PATHS ON THE NEW DOMAIN: / is a new home page, /search, /voters, /check, /about, /v/{uid}/{code} for a report. Internally every page is still /electoral/..., because fifteen modules and several thousand lines of inline browser JavaScript say so and rewriting them by hand is how a link quietly breaks. brand.js does it in two functions that face each other: toInternalPath() on the way in, localizeHtml() on the way out, applied once in pageShell.
- ⚠⚠ AND /api/electoral/* IS NEVER REWRITTEN, WHICH IS THE ONE WAY THAT COULD BREAK EVERY FETCH ON THE SITE. The rewrites are anchored on the character before the slash, so the /check inside /api/electoral, preceded by an i, cannot match. Six shapes of that call are pinned in test/citizen.mjs.
- ⚠ THE SITE IS PASSED AS AN ARGUMENT, NEVER STORED IN A MODULE VARIABLE. A Worker isolate interleaves concurrent requests at every await and the electoral router awaits D1 before it renders, so a stored 'current site' would let two overlapping requests swap it under each other and render a citizen page with the court brand.
- ⚠⚠ A NEW HOME PAGE THAT MAKES NO REGISTER CALL. Search first: full name, father's or husband's name, state, and nothing else until there are results to narrow, the relative is asked for second because on a common name it is the single most separating thing a reader can type. Below it the national coverage, read from D1 alone by the new coverageFromStore(), and the page is held at the edge, so the common case never reaches the Worker.
- ⚠ THE COVERAGE READINGS ARE ON THE CRON NOW (sweepStateCoverage, three states a run, oldest first). They used to advance only when somebody opened the roll explorer, which was survivable when one page used them and is not now that the home page prints the national total: 'nobody visited the map today' would read as 'the roll stopped growing'.
- ⚠⚠ THE EDGE CACHE IS BACK ON THE VOTERS API, PER PART, AND THE EARLIER CALL AGAINST IT IS RECORDED RATHER THAN ERASED. It was left off because the roll was moving 1.3% an hour and correctness beat the saving. The roll has not moved for days, every part now carries the timestamp of the moment it was computed (so a cache hit reports its true age, not the age of the hit), and the page's Refresh control sets refresh=1 and bypasses the cache entirely. rows 1h, analysis/notable/booths/acs 4h, geo 1h, summary 15min because it is the figure a reader quotes, coverage 15min. Four hours remains the ceiling and test/citizen.mjs fails on any part over it, the first cut put analysis at six because it is the slowest card, which is a reason to want a longer hold and not a reason to be allowed one.
- ⚠ The static shells (home, search, explorer, community map, version) are edge-cached for ten minutes with a day of stale-while-revalidate. A report page may never take a cache: it names a private individual, carries the requester's email and is reached by an unguessable link, so no-store is the default and a page has to ask.
- ⚠ The search form is three fields and a door: name, relative, state, with relationship, address, age, sex and matching mode behind 'More ways to narrow it', which opens itself when a shared link already carries one of them, or the reader sees three empty boxes and a count they cannot account for.
- ⚠⚠ THE MARK IS AN INKED FINGER AND DELIBERATELY NOT A CHAKRA, AN EMBLEM OR A MAP. The first two are restricted under the Emblems and Names Act 1950 and the State Emblem of India Act 2005, and borrowing one would invite a reader to believe this is a government service, which it is not and which the footer says on every page. A map outline carries its own hazard: boundary depiction is legally sensitive and a stylised outline invites a complaint about a line nobody meant to draw.
- ⚠ Report email now links indiancitizen.app and signs as Indian Citizen, with Reply-To contact@indiancitizen.app. The From address stays on the SES-verified domain until indiancitizen.app is verified in SES ap-south-1; MAIL_FROM_CITIZEN switches it with a var change and no code deploy.
- ⚠ A CSS COMMENT IN BASE_CSS NAMED THE OTHER PRODUCT, and BASE_CSS ships inside every page of both. Found by the new gate, not by reading. Same trap as the e1.10.x rule that no stylesheet comment may name a register.
- ⚠ THE BACKTICK-IN-A-TEMPLATE-LITERAL TRAP, SIXTH AND SEVENTH TIME, both in comments written this release. And a new one: an HTML comment explaining why a court page must not say 'electoral roll' shipped inside the page and tripped that gate on the sentence describing it. A comment in a rendered string is page content.
- Gates: citizen 238 (every assertion confirmed to fail when its fix was reverted) · electoral-voters 617 · electoral-page · electoral-link 77 · electoral-calibration · calibration 658 · fixtures 3/3 · models 119 · model-lock · role-render · api-json.
2026-09-01
e1.11.0, Half of Uttar Pradesh could not be opened by its own voter ID
- ⚠⚠ THE REGISTER STILL CARRIES OLD SLASH-FORMAT VOTER IDS BESIDE THE MODERN ONES, AND MARKS BOTH VALID. Measured on Saharanpur AC 4 part 360: 46 of 100 electors on one page read UP/84/417/0729095 rather than NEZ3149994, and every one of them carries epicValid true, they are real numbers, not scanning damage.
- ⚠⚠ BUT THE VOTER-ID LOOKUP IS A PATH ENDPOINT, so a slash either splits the path (404) or arrives percent-encoded and matches nothing (HTTP 200, zero persons). So every oldest-voter, youngest-voter, biggest-household and parent link on those rows opened an error page saying no elector holds that ID. The founder found it on the Saharanpur oldest-voter list.
- ⚠ AND epic= IS NOT A SEARCH FILTER EITHER, so there was no second door, measured, it returns 0, and an unknown parameter on this API is ignored rather than rejected, which is exactly how that would have looked like it worked.
- ⚠ THE HOUSEHOLD TRIPLE IS A PUBLISHED PRIMARY CRITERION AND REACHES THEM: roll 3292, part 360, house 124 returns the 101-year-old at serial 180. Links now carry where the row sits (at=roll.part.house) and elEpicResolve falls back to the household when the ID will not fetch.
- ⚠⚠ THE HOUSEHOLD IS HOW WE ASK; THE VOTER ID IS STILL WHO IT IS. A row from that household is accepted only when its own EPIC is the one requested, so a stale or tampered hint can fail to find somebody and can never find the WRONG somebody. Gated, and the gate was confirmed to fail when the check was removed.
- ⚠ Applied at all three doors: the voters page cards and voter list, the public roll search, and the identify links inside a report.
- ⚠ THE BACKTICK-IN-A-TEMPLATE-LITERAL TRAP, FIFTH TIME. A comment written inside the page script quoted a path in backticks and ended the whole literal. Only node --check finds it.
- Gates: electoral-voters 614.
2026-09-01
e1.10.9, The state view painted its own districts over, and never showed the number it held
- ⚠⚠ THE DISTRICTS ARRIVED AND WERE PAINTED OVER. `render()` runs before part=geo resolves, so it drew the ranked district list from an EMPTY map; the geo handler then set the whole KPI box to `g.note`. A state still filling in always has a note, so on every large state the districts landed and were immediately overwritten by one sentence, and the page read as having no data at all. The note is state now, and render() runs again when the data lands and draws the note beside the list rather than instead of it.
- ⚠⚠ AND THE STATE VIEW NEVER SHOWED THE STATE'S OWN NUMBER, WHICH IT HELD ALL ALONG. `part=summary` refuses above district scope, so `?state=S24` offered a dropdown and a sentence explaining that a whole state is more than the roll will add up, while `el_ac_counts` held Uttar Pradesh at 123,502,261 across all 403 of its constituencies, and the coverage map on the same site was publishing exactly that. This is e1.6.4's lesson (a state too large to add up is not a state with no number) arriving on the second page that needed it.
- ⚠ THE SEAT SUM OUTRANKS THE DISTRICT FLOOR WHERE IT IS BIGGER, and never because it arrived last: the cron re-measures every seat in the country, so a seat sum is current, while the district table only advances when somebody browses that state. Both are floors, an unmeasured seat contributes nothing, and the page says which it counted and how much of the state it covers.
- Gates: electoral-voters 607.
2026-09-01
e1.10.8, A hundred and ten seconds of it was asking a question already answered
- ⚠⚠ THE DISTRICT DROPDOWN FOR UTTAR PRADESH STILL TOOK 117 SECONDS AFTER e1.10.7, AND ALMOST NONE OF IT WAS THE DISTRICT WALK. Before probing districts, part=geo asks the state itself whether it can be aggregated. `enumTerms` yields one candidate term per word of the state's name, and a FAILING call walks all of them, so Uttar Pradesh paid the full 55-second gateway for "Uttar" and another for "Pradesh", to be told twice what the first call had established. 55 + 55 + the 7-second head start is the whole of it. `maxTerms: 1` now.
- ⚠ THE RULE WAS ALREADY WRITTEN IN THIS FILE, FOUR LINES ABOVE A CALL THAT IGNORED IT. `coveragePart` carries the comment "ONE TERM, NOT THE LADDER, a failing probe walks every candidate term for that state, so a two-word state doubles its own timeout". It was applied to the background survey and never to the one call a reader waits on.
- ⚠⚠ AND THE FAILURE WAS NEVER REMEMBERED, SO EVERY VISIT PAID IT AGAIN. There is a guard meant to skip this call for states known not to aggregate, it reads the coverage status, and `coverageReading` CARRIES THE PREVIOUS STATUS FORWARD when a probe fails. That is right about the count (e1.6.5 exists because a failed re-probe was erasing good numbers) and wrong about the verdict: Uttar Pradesh answered at state scope back when it held 445,324 voters, so it has reported `indexed` ever since, through 47 million, and the guard could never fire for exactly the states it was written for.
- ⚠ SO THE MEASUREMENT IS RECORDED AS A SEPARATE FACT: `hard_at`, the last time a state-scope aggregation actually failed here. A separate column rather than a reinterpreted status, because flipping `status` to `unknown` would carry a real count into the bucket the map draws as "not measured yet", losing a number to fix a latency bug. It EXPIRES after six hours, since the fault being fixed is a verdict that never did, and a fresh successful probe clears it. `carried` is bound into the upsert rather than inferred, because a carried row also carries a non-null count and the two are indistinguishable from inside the statement.
- ⚠ CORRECTING e1.10.7: the 62 seconds it measured on Sikkim, and blamed on the zero double-check, was this same pre-flight, one term, one gateway, plus the head start. The double-check was removed on a second wrong attribution. It is not restored here, on its own merits rather than on that reasoning: 99 codes of which most are not districts, and the upsert already refuses to let a zero replace a positive count.
- ⚠ The district-scope call in `acsPart` keeps its ladder and its retry: it measures at 0.58 seconds on Bankura, so the argument above does not apply to it. The gate is scoped by the gateway ceiling so it cannot drift onto that one.
- ⚠ THE FIRST VERSION OF THE NEW GATE PASSED WITH THE FIX DELETED, it sliced the source to the next blank line and stopped before the call it was checking. A gate that survives the removal of the thing it guards is not a gate. Both new gates were then confirmed to fail on revert.
- Gates: electoral-voters 603.
2026-09-01
e1.10.7, The retraction: I read the wrong key and called it corruption
- ⚠⚠ e1.10.6 SAID THE DISTRICT WALK HAD ZEROED MUZAFFARNAGAR, SAHARANPUR AND MEERUT. IT HAD NOT. Nothing was corrupted. The geo payload carries a district's count under `occ`, and the throwaway script I was reading it with printed `n`, which is absent, so every district printed as 0. The register was right, the table was right, and the only thing broken was my own reading of my own output. Shahjahanpur 1,923,061 and Muzaffarnagar 1,890,608 were in that response the whole time.
- ⚠ THE FALSE MEASUREMENT IS RETRACTED FROM THIS LOG BECAUSE THAT IS WHAT THIS LOG IS FOR. A wrong number recorded here outlives the mistake and is read later as evidence; leaving it in would be worse than never writing it.
- ⚠ THE DOUBLE-CHECK ON A ZERO, ADDED IN e1.10.6 ON THAT PREMISE, IS REMOVED. The state coverage probe confirms a zero across 37 real states, where a zero is a strong claim and cheap to re-ask. The district walk covers 99 two-digit codes of which most are simply not districts, so confirming every zero doubles the cost of the ordinary case, measured, it took a small state's walk to 62 seconds, which is time a reader spends waiting for a dropdown.
- ⚠ WHAT SURVIVES IS THE CHEAP HALF, AND IT SURVIVES AS A GUARD RATHER THAN A FIX: the upsert still refuses to let a zero replace a positive count. The counted flag from e1.6.6 catches a body with no count and cannot catch one carrying a real zero, so the invariant is worth holding, it just is not a response to anything that happened.
- ⚠⚠ THE ONE REAL FAULT IN e1.10.6 WAS THE LATENCY, AND THAT FIX HOLDS. part=geo for Uttar Pradesh answered in 118 seconds, twice, and none of it was counting: district NAMING runs inline after the probe's head start, ten districts at three lanes, each able to take the full timeout. Bounded by a wall clock now, the same call answers in 11.8 seconds.
- Gates: electoral-voters 596.
2026-09-01
e1.10.6, RETRACTED, a corruption that did not happen (see e1.10.7)
- ⚠⚠ THIS RELEASE SHIPPED AND ITS HEADLINE CLAIM WAS WRONG. It reported that the district walk had zeroed Muzaffarnagar, Saharanpur and Meerut under contention. Nothing was corrupted: the geo payload carries a district's count under `occ`, the script reading it printed `n`, and every district therefore printed as 0. The entry stays because the version shipped and a report made under it has to be traceable to the rules that produced it - but the measurement in it is withdrawn, and e1.10.7 explains what actually happened.
- What e1.10.6 did that was right, and is kept: district naming is bounded by a wall clock. part=geo for Uttar Pradesh had been answering in 118 seconds with none of it spent counting, and answers in 11.8 after.
- What it did on the false premise, and is removed at e1.10.7: confirming every district zero with a second call.
2026-09-01
e1.10.5, The same rotation bug, in a third place, written by me an hour earlier
- ⚠⚠ e1.10.4 PUT DISTRICT DISCOVERY ON THE CRON AND ITERATED THE STATES IN KEY ORDER, BREAKING ON A BUDGET. That gives the whole budget to S01, S02, S03 and never reaches S24 - which is the one state the release was written for, sitting at six district codes out of seventy-five. Verified live after deploying: Uttar Pradesh still returned 3 districts and 96 pending. This is the e1.6.4 rotation bug for the THIRD time, in the third function, and I wrote this one an hour after fixing the second.
- The order is now by how little is known: states are ranked by how many district codes are on record, fewest first, and a state whose walk is finished is dropped from the queue entirely. Gated - the state with six codes must be served before the one with forty-five.
- ⚠ THE LESSON IS NOT THE BUG, IT IS THAT A LOOP WITH A BUDGET IS A ROTATION AND EVERY ROTATION HERE HAS BEEN WRONG. Any batch that walks a fixed list and stops when time runs out serves the head of that list forever. Ordering by need is the only shape that has survived: state coverage (e1.6.4), the seat queue (e1.8.1), and now the district walk.
- Gates: electoral-voters 594.
2026-09-01
e1.10.4, Uttar Pradesh has 75 districts and the walk stopped at 45
- Founder asked why /voters?state=S24 does not show all districts. Three faults, stacked, and together they left the largest state in India offering three districts out of seventy-five.
- ⚠⚠ MAX_DISTRICT_PROBE WAS 45 AND A districtCode IS THE STATE CODE PLUS TWO DIGITS. Uttar Pradesh has 75 districts, so codes S2446 to S2475 were never asked about at all. Measured 2026-09-01: those codes hold 28 districts and 44,628,990 voters - Lucknow 3,051,884, Hardoi 2,621,111, Kheri 2,561,784, Kushinagar, Bahraich, Ayodhya, Barabanki - none of which could ever reach the dropdown. The bound is 99 now, because that IS the address space. ⚠ Not a per-state table of district counts and not a walk that stops at the first run of empty codes: a table is another number to keep right, and a walk that stops early reads a partial ingest as the end of the state, which is the AC_SEATS reasoning and just as wrong here. Covering the whole space is cheap - an unused code answers in about a third of a second.
- ⚠⚠ AND THE QUEUE WAS SLICED IN CODE ORDER, WHICH IS THE e1.6.4 BUG SITTING IN A SECOND FUNCTION THE WHOLE TIME. Six codes per visit taken from the head of the list means the early codes, already known, expire and get re-asked ahead of codes never asked at all. Uttar Pradesh had SIX rows on record, the highest S2406. Never-probed first, then oldest.
- ⚠ A BATCH IS BOUNDED BY TIME NOW, NOT ONLY BY COUNT. Most of the two-digit space is unused and answers instantly, so a fixed six spent a whole visit discovering nothing; twenty codes under a wall clock clears the empty stretches in one visit and still stops before the runtime does.
- ⚠⚠ AND THE DISTRICT LIST ONLY EVER ADVANCED WHEN SOMEBODY OPENED THAT STATE. A state nobody visits had a dropdown that never filled, and the way to fix it was to load the page a dozen times. The seat sweep learned this at e1.8.1; the district walk never did. It rides the cron now, behind the seats and inside the same claimed window - one sweep at a time is the rule the register's rate limit imposes (e1.9.4), so it takes what is left of the quarter hour rather than opening its own lanes.
- ⚠ The budget cut-off returns null and writeDistrictCounts binds every row it is handed, so one null would throw inside its try/catch and silently lose the whole batch - the write is guarded precisely so it can never break a page, which is what would have made that invisible. Filtered.
- Gates: electoral-voters 590.
2026-08-29
e1.10.3, The relationship chip printed across the column beside it
- ⚠️ e1.10.2 gave the per-generation family tables one fixed column grid, which was the fix; the relationship chip is nowrap, which is right for Brother or Mother; and the subject's own chip reads "The person searched for". Together those three make the chip run straight across the Born column. Found on the live page minutes after shipping, by looking at it.
- The chip wraps inside the family tables only, and the Relationship column is a little wider. Both halves are gated together, because a fixed layout is what makes a nowrap chip dangerous and the next person to add a column will not know that.
2026-08-29
e1.10.2, The one place a colour carries a fact was using the colour that carries none
- ⚠️⚠️ FEMALE ELECTORS WERE DRAWN IN THE PRODUCT'S ACCENT TEAL. It sits one hue from the blue used for the men, and it is the same teal already used by every eyebrow, every spouse link, every generation label and the timeline dots on the same page, so on a family tree, where the sex of each box is read off a 4px bar, the distinguishing colour was the one colour that distinguishes nothing. Female is dark pink now, far enough from both the blue and the maroon severity chips to survive a greyscale print as a plainly darker stroke.
- ⚠️ ONE DEFINITION, THREE READ SITES: the box outline, the sex bar inside it, and the key under the drawing. The key had been written out twice, once for the electoral report and once for the legal one, so a colour changed in the drawing had three places to change and two of them were legends. It is one function now.
- ⚠️ THE LAST LINE OF AN ADDRESS IS STILL THE ADDRESS. City, district, state and PIN were set in slate on the voter card while the lines above them were graphite, so the one line a reader scans for the place read as a footnote to it. Same type as the lines above; only the spacing separates it.
- The per-generation family tables share one fixed column grid. Grandparents, Parents and This generation are three separate tables stacked down the page, and an auto-layout table sizes its columns from its own contents, so a generation holding one short name drew a narrow Name column and a wide Sex column, and the reader had to re-find every column at each heading.
- Nothing here reaches a scorer: no identification, band, edge or field moves. Gates: electoral-calibration 260, electoral-voters 589, electoral-page, electoral-link 64, the colour, the shared legend and the shared column grid each confirmed to fail when reverted.
2026-08-29
e1.10.1, The locality knows its own district, and an address rarely writes one the way the register does
- ⚠️⚠️ EVERY ROUTE TO A DISTRICT NEEDED THE REGISTER'S FULL NAME FOR IT TO APPEAR IN THE TYPED ADDRESS, AND ADDRESSES DO NOT TALK THAT WAY. A Bangalore address says "Bangalore"; the roll's district is "BANGALORE URBAN", so the containment test failed and the district was never grounded at all. Measured live on Kartik Mandaville: locality Haraluru matched, districtName null, districtHits null, and the entire geographic weight fell on the state.
- Where a section name from the address HAS matched, the rows carrying it are the subject's own neighbourhood and the register prints their district. That is a subject fact, it comes from the locality the requester typed, not from whichever candidate the scorer happens to favour, and it is finer than the address's own district word, because a locality knows which side of a boundary it sits on.
- ⚠️ FILLS ONLY, NEVER OVERRIDES. A district the requester stated stands: a stated geography is a claim, not something to reconcile silently. ⚠️ One district or none, a locality resolving to two districts resolves to neither. ⚠️ And a row in another state cannot name the subject's district.
- ⚠️ Safe to filter on, which is the e1.6.7 requirement: the name comes from rows the register itself returned, so it is a district the register demonstrably uses.
- Live effect: Kartik Mandaville goes from district null to district 1 measured, so the geography edge is measured at district level rather than falling back to the state. Goldens unmoved, Sanjay Kumar Das 99% (district 3), Vassant Madkaikar 99%, Vijender Dabbeta 99%, Ramesh Kumar still NOT IDENTIFIED, Rajesh Kumar still 94% probable with 789 measured in Central Delhi.
- Gate: electoral-calibration 260, confirmed to fail when the rule is removed.
2026-08-29
e1.10.0, How much a place is worth is a measurement, and being the only one of your name in it is an identification
- ⚠️⚠️ THE ENGINE WAS ALREADY MEASURING THIS AND THEN IGNORING IT. `DISTRICT` and `STATE` were flat rungs, a district agreement paid 8 and a state agreement 2, whoever the subject was. Measured live, that is not one fact but two opposite ones: Kartik Mandaville is the ONLY elector of his name in Karnataka out of ten in India and was paid 2, damped to 1.62; Rajesh Kumar shares Delhi with 14,113 of his own name and 789 in Central district, and was paid the same 2 and 8. The v6.2 lesson, weigh a thing by how common it MEASURES, not by what type of thing it is, arriving in geography.
- The edge is now `N / hits`, weighed against the same clamped population the prior was taken from, so `prior x lr` lands exactly on "one of the people of this name who live here" and nothing else. Computing it against the raw national count while the prior used a clamped one is the double-ceiling bug relativePairEdge was fixed for at e1.7.1, and it is pinned by its own assertion.
- ⚠️ IT MOVES IN BOTH DIRECTIONS AND THE DOWNWARD ONE IS THE POINT. A state 14,000 of your namesakes live in now measures about 1.4 instead of a flat 2, which is what it is worth. Srinivas Reddy correspondingly went 98% to 96%, still confirmed, his state is crowded and the old number was flattering him.
- ⚠️⚠️ AND BEING THE ONLY ONE OF YOUR NAME WHERE YOU LIVE IS NOW `identifying`. That flag means something distinguishes this elector from others of the same name, and a measured count of ONE bearer in the state or district the subject is known to live in is precisely that. It had not been counted, so Kartik Mandaville, one Kartik Mandaville in Karnataka, out of ten in India, scored 92% `identifying: false` and was refused, because nothing father-shaped had fired and a locality is not an anchor.
- ⚠️ THE NEW BRANCH IS SELF-LIMITING, WHICH IS WHY IT IS SAFE. It can only open where the register itself measured one bearer in the place, so on the names that need protecting from it, 14,113 Rajesh Kumars in Delhi, it can never open. It also requires the name to have matched on its own merits (exact name, sound given name, no surname conflict, no contradicted relative), so a fuzzy variant cannot inherit the uniqueness of a name it merely resembles. And an unmeasured count is not a one.
- ⚠️ MEASURED LIVE ON THE GOLDENS BEFORE SHIPPING, because this touches every identification the product makes. Sanjay Kumar Das 99% confirmed UNMOVED; Vassant Madkaikar 99% confirmed UNMOVED; Vijender Dabbeta 99% confirmed; Srinivas Reddy 98% to 96%, still confirmed; and the two that must not move: Ramesh Kumar s/o Parshuram in Delhi NOT IDENTIFIED, and Rajesh Kumar s/o Ram Avtar still 94% PROBABLE, not confirmed, so it still cannot anchor a legal check.
- The case it was written for: Kartik Mandaville with NO related person on the form went from 92% probable to 97% CONFIRMED on the same voter ID. That is the subject an LDD check helps least, resolved from a name and a place alone.
- Gate: electoral-calibration 256, with both the measured edge and the unmeasured-is-not-unique rule confirmed to fail when reverted.
2026-08-29
e1.9.6, An absent middle name is not a different middle name
- ⚠️⚠️ THE MAN HIMSELF AND A MAN WITH A DIFFERENT FATHER SCORED IDENTICALLY. Measured on the subject `VASSANT TUKARAM MADKAIKAR`: the roll row `VASSANT MADKAIKAR`, which is him, scored 0.817 at tier `far`, and the row `VASSANT PANDURANG MADKAIKAR`, which is a different man, scored 0.817 at tier `far`. The middle token found nothing to match in either case and was paid the same for it, so an entry that simply omits a patronymic was indistinguishable from one naming somebody else's father.
- One of those is an ABSENCE and the other is a CONTRADICTION, and they are not the same evidence. The court engine has drawn that line since v7.6.5; this scorer never had. After the change they are 0.950 and 0.817, the contradicted row does not move at all.
- ⚠️ THE SEPARATOR IS WHETHER THE ROW HAS ANYTHING TO OFFER IN THAT POSITION. The lift applies only where no token on the row is left unexplained, which is precisely the case where the roll recorded no middle name. A row carrying its own middle name has a rival for the slot and keeps the old score, whatever that name is.
- ⚠️ It re-prices MIDDLE tokens only, and only when the given name AND the surname both match on their own merits. It can never rescue a row whose given name or surname belongs to somebody else, those stay at 0.633, and a two-token subject cannot reach the branch at all, so every existing two-token subject scores exactly as before.
- ⚠️ An exact full-name match still outranks it: 1.000 against 0.950. A patronymic the roll also carries and matches is more evidence than one that could not be checked.
- ⚠️⚠️ IT IS ONE-DIRECTIONAL, SO THE LIVE GOLDENS ARE THE OTHER HALF OF THE GATE. The branch can only ever raise a score, which means it can attribute rows the old code did not. Measured live before shipping: Sanjay Kumar Das (Odisha) 99% confirmed with 16 people across 3 generations, UNMOVED; Srinivas Reddy (Telangana) 98% confirmed, UNMOVED; Ramesh Kumar (Delhi) not identified before and after, confirmed by an A/B with the branch disabled, that is the index having grown, documented at e1.9.0, not this release.
- And the case it was written for: `Vassant Tukaram Madkaikar` s/o `Tukaram Mahadev Madkaikar` went from NOT IDENTIFIED to **99% confirmed on HNL1373927**, the same voter ID a hand-run electoral check reaches, with his father, mother, brother and sister-in-law placed. A/B'd with the branch disabled, which returns NOT IDENTIFIED, so the change is the cause.
- This is the second half of e1.9.5. That release fixed the QUERY side, so the searches reached him, the log showed `VASSANT MADKAIKAR` returning 15 electors and the father's five households opened, 41 electors scored, and then the scorer confirmed none of them. Both halves were the same fault: a court register writes the full formal name with the patronymic in the middle, and the roll files people as given plus surname.
- Gate: electoral-calibration 252, the primary assertion confirmed to fail when the branch is disabled.
2026-08-29
e1.9.5, A relative's middle name is dropped, because the roll does not carry one
- ⚠️⚠️ ONE EXTRA MIDDLE TOKEN TAKES A REAL MAN FROM 42 ELECTORS TO NOUGHT. Measured live against the register: `name=Vassant Madkaikar` returns 42 people, 15 of them in Goa; `name=Vassant Tukaram Madkaikar` returns 0. `name=Sushma Madkaikar` returns 233; `Sushma Tukaram Madkaikar` returns 0. The zero holds under `fuzzy` as well as `all`.
- ⚠️ AND IT IS NOT A SPELLING PROBLEM, WHICH IS WHY NOTHING ELSE CAUGHT IT. `Vassant Madkaikar` and `Vasant Madkaikar` both return exactly 42, the register folds the doubled consonant itself, exactly as e1.2.5 measured when it retired the orthographic variant wave. What the register will not do is invent a token the query does not have, and `nameMatchMode=all` requires every token to match.
- `structuralVariants` has dropped the middle token of the SUBJECT's name since e1.2.5 for precisely this reason. `relativeQueryForms` was never given the same treatment: it drops a leading initial and appends the subject's surname, and stops there. It now also offers the given name and the surname without the middle.
- ⚠️ The live consequence is the whole point. A legal check on `Vassant Tukaram Madkaikar` whose form names the father as `Tukaram Mahadev Madkaikar`, the full formal patronymic a COURT register carries, measured zero electors holding that pair and reported that no elector could be matched, while the same man searched under `Tukaram Madkaikar` identifies at 99%. The convention this breaks on (given name, father's given name, surname) is the norm across Maharashtra, Goa and much of the Marathi and Konkani west.
- ⚠️ APPENDED, NEVER SUBSTITUTED. The full form as given is still asked first and stays first in the list; this is one more question, reached only on a zero, since the caller retries at most twice and only when a form returned nothing. ⚠️ And it can never yield a single token, one word names a clan, in this product as in the court engine.
- Gate: electoral-calibration 246, confirmed to fail when the line is removed.
2026-08-29
e1.9.4, Two sweeps at once fail everything, instantly and silently
- ⚠⚠ A FORCED SWEEP RUN WHILE THE CRON WAS ALREADY GOING RETURNED 10 MEASURED 0, FAILED 10, IN 4.9 SECONDS. Far too fast to be timeouts: the register 429s at about ten calls in flight and each sweep uses six lanes, so two together fail every call. Nothing is written, nothing is lost - the carry-forward sees to that - and no progress is made either, which is the worst of the three because it looks like work.
- ⚠ THREE CALLERS CAN START A SWEEP and nothing stopped them overlapping: the cron, a page request with no state due, and the forced endpoint. el_sweep_lock is one row and the claim is a conditional UPDATE whose row count IS the answer - reading a timestamp and then writing it is two statements with a gap between them, and the gap is exactly where two crons meet.
- ⚠ THE LOCK LIVES FOR THE CALLER'S OWN BUDGET, NOT A FIXED SPELL. A ten-minute cron run and a twenty-second page batch need different windows, and each has already declared one. And it expires by itself: the page-side batch is routinely killed mid-run when the runtime stops background work, and a lock it still held would block every later sweep until somebody noticed.
- ⚠ THE GATE THAT CAUGHT THIS RELEASE'S OWN MISTAKE: the version was bumped and the log entry did not land, and test/electoral-page.mjs failed with "the live version has no entry in the change log, so a report made by it cannot be traced to its rules". That is exactly what it is for.
- Gates: electoral-voters 589 - a second sweep inside the first one's budget declines rather than racing it, and a claim aged past its budget lets the next one through.
2026-08-29
e1.9.3, A ceiling below the register's own selects against the biggest constituencies
- Founder asked why Maharashtra reads so low. The number is right and the state is nearly empty: 385,710 in Baramati plus 100,529 in Rahuri is 486,239, exactly the state-scope figure, and only 2 of its 288 constituencies are in the index at all.
- ⚠⚠ BUT LOOKING FOR THAT FOUND THE SEAT SWEEP LOSING BARAMATI ENTIRELY. It holds 385,710 voters and takes 30 seconds to say so, against a 20-second ceiling in the sweep, so it was never written: Maharashtra's stored seat sum was 100,529 from its one small seat, and the ONLY thing that stopped the page publishing that was the floor rule refusing a figure smaller than the state count it already had. A cut-off below the server's is not caution - it selects against exactly the constituencies that hold the most people, and it does so silently, because a seat that times out simply has no row.
- ⚠⚠ THE REGISTER GIVES UP AT ABOUT SIXTY SECONDS, SO NOTHING SHOULD GIVE UP LATER, OR WITHOUT A REASON EARLIER. EL_GATEWAY_MS is 55s and every background measurement now takes it: the cron sweep, the forced sweep endpoint, and the geo drill that fills the district dropdown (which was abandoning at 20s and falling over to the far more expensive district-by-district walk).
- ⚠ A SHORTER CEILING SURVIVES ONLY WHERE THE LIMIT IS NOT THE REGISTER, and there are exactly two such places. Work in the tail of a request is cut by the runtime about half a minute after the response, so the page-side seat batch and the coverage probe keep 20-25s - at the gateway ceiling they would finish nothing and write nothing, which is the e1.6.5 failure. And a reader watching a card cannot be made to wait a minute.
- ⚠ EL_TIMEOUT, the report pipeline's own ceiling, goes from 12s to 25s. Its comment claimed 12s was comfortably above every healthy call; that was true when a search answered in under a second and the index has since grown by two orders of magnitude. A report making 150 calls still cannot let each burn the full gateway, but at 12s the pipeline was abandoning measurements the register was about to return - and an unmeasured anchor does not slow a report, it changes what the report concludes (e1.7.1).
- Also settled, because it was worth ruling out rather than assuming: no row escapes the state or constituency filters. National BRIJESH KUMAR is 91,874 occurrences and its stateCode facet sums to 91,874 across 26 codes; Assam's acNumber facet sums to its exact state total across 17 seats; Maharashtra's to its exact total across 2. Nothing is hiding behind a missing or mistyped code.
- Gates: electoral-voters 586.
2026-08-29
e1.9.2, A territory with no assembly still has a constituency, and an empty state can be proved wrong
- Founder asked whether Haryana is in the index yet. It is not, and it was worth proving four ways rather than trusting one: q=Haryana at state scope is 0, a name search under stateCode=S07 is 0 and does not depend on the q term at all, and two individual constituencies are 0. Searched NATIONALLY with no state filter, every Haryana place name resolves to a neighbouring roll - Faridabad's 18,536 are all Delhi addresses, Ambala's are Telangana and Rajasthan, Gurugram and Rohtak are zero everywhere. There is no pool of Haryana rows under any state code.
- ⚠⚠ BUT AUDITING THE STATE-CODE TABLES FOR IT FOUND FOUR TERRITORIES WITH NO WAY DOWN. AC_SEATS excluded every union territory with no legislative assembly, which reads as principled and is wrong: the register files an assembly-less UT under its PARLIAMENTARY constituency and returns it in acName. Andaman's AC 1 is "A & N ISLANDS PARLIAMENTARY CONSTITUENCY" and holds the whole territory; Chandigarh's AC 1 is Chandigarh. Excluding them removed the descent, so each could be counted only while it stayed small enough for a state-scope query - which is precisely the condition that has been expiring state by state all week.
- ⚠⚠ AND DADRA AND NAGAR HAVELI AND DAMAN AND DIU IS TWO CONSTITUENCIES, NOT ONE. Measured: AC 1 Daman & Diu 134,897 and AC 2 Dadra & Nagar Haveli 287,636, summing to 422,533 against a state-scope 422,527. Had it been added as a single seat the page would have counted a third of the territory and called it whole.
- ⚠⚠ A MEASURED SEAT COUNT NOW OVERRIDES `empty`, WHICH THE DISTRICT FLOOR DELIBERATELY DOES NOT. The district table is written only when somebody browses that state, so a stale row there could resurrect a roll that has genuinely been withdrawn. The seat table is refreshed by the cron on its own, so a positive number in it is a current measurement rather than an old inference - and a current measurement that a state holds voters outranks a probe saying it holds none. Without this a state could sit at "no roll in the index yet" while its constituencies were full, which is the e1.6.4 failure with a newer cause, and not hypothetical: q=<state> has to match the state's name in the row text, and Maharashtra has already shown that a state-scope query can be answering about a set that does not contain the rows the seats find.
- ⚠ The superseded J&K code S09 stays out of the seat table - it predates the 2019 split, its AC 1 returns nothing, and giving it seats would sweep a dead code every cycle. The current UT keeps its post-2022 ninety.
- Audited and clean: all 37 codes present in ECI_STATES, STATE_Q and CENSUS_OF_ECI; AC_SEATS is now 36 entries and 4,129 seats; every code in the seat table is asserted to be a real ECI code, because a typo there is a state that is silently never counted and looks perfectly fine on the page.
- Gates: electoral-voters 583 (was 544).
2026-08-29
e1.9.1, The cron does fire, and it was refreshing slower than the index grows
- ⚠ THE e1.8.2 NOTE THAT THE CRON DOES NOT FIRE IS WITHDRAWN. It does. Measured on el_ac_counts, writes cluster from each quarter-hour boundary - 00:02, 00:17, 00:33, 00:47 - which is the schedule. It had simply not begun by the time it was first checked, minutes after the deploy. The page-side batch and /api/electoral/sweep stay: neither costs anything when the cron is doing the work, and the sweep endpoint is how the question was settled.
- ⚠⚠ BUT THE CYCLE WAS SLOWER THAN THE THING IT MEASURES. About 250 seats an hour against the ~4,100 seats now in the table is a full cycle of roughly sixteen hours, on an index that is doubling in a day: Uttar Pradesh went from 47,908,253 to 97,620,985 between two sweeps of it. A figure that takes sixteen hours to come round, on a store that doubles in twenty-four, is stale by half before it is refreshed.
- ⚠ THE BUDGET WAS THE BINDING CONSTRAINT, NOT THE LIMIT - runs were finishing 60 to 100 seats inside 240s, nowhere near the 150 ceiling - so the budget goes to ten minutes and the limit to 400. ⚠ AND THE CONCURRENCY DOES NOT MOVE: the register 429s at about ten calls in flight, and a cron racing page traffic for lanes measures less rather than more. Ten minutes is inside the fifteen a scheduled invocation may run and leaves no overlap with the next.
- Live at the time of writing: 435,268,766 voters counted, from 384,673,226 nine hours earlier and 1,592,768 two days ago. Uttar Pradesh 97,620,985 and now seat-counted at 403 of 403, which is the descent doing exactly what it was built for - the state-scope figure it replaced was 47,908,253.
2026-08-29
e1.9.0, Five fields the register was handing us, read at last
- Findings: FINDINGS-electoral-schema-audit.md - the Solr schema of the elector index, audited against every line of electoral-*.js and against 5,313 real occurrence rows pulled from five whole roll parts in five states (Delhi AC 6 part 134, Odisha AC 36 part 6, Maharashtra AC 201 part 96, Assam AC 113 part 38, Rajasthan AC 182 part 51). The query side is used close to its published limit. The ROW side was not: of the 33 keys on every occurrence, four carrying real evidence were read nowhere.
- ⚠⚠ A POLICE STATION IS NOT A LOCALITY, AND IT WAS PAYING THE SAME EDGE AS A STREET - AND UNLOCKING A HARD ANCHOR. The locality test ran a substring match over `fullAddress`, which is not an address line but every address component concatenated. One Assam row reads "26, DITTOKCHERRA MIDING, dittokcherra rly.colony, harangajao, harangajao itdp block, DIMA HASAO, Assam, 788818" - house, section, post office, POLICE STATION, BLOCK, district, state, PIN. Measured in that state: `dittokcherra`, the section and his actual locality, is carried by 2,485 electors; `harangajao`, the police station and the development block, by 53,960. Both paid LOCALITY 25, and a locality hit is what unlocks HOUSE_EXACT 200 `hard` - so a subject who wrote their police station in the address box and shared a house number earned a hard anchor. The guard above it can only reject a locality that is the district or the state, because a police station's name is nowhere in the subject.
- The match is TIERED against the roll's own smallest named place: only `sectionName` / `sectionNameLatin` is the locality; a hit anywhere else in `fullAddress` is the administrative furniture around it and pays LOCALITY_WIDE 4, deliberately BELOW the district edge that has already been paid for the same fact, and it cannot unlock the house anchor. ⚠ `pollingStation` is gone from the haystack entirely - a school is where they vote, not where they live. This is the court engine's v6.3.6 police-station trap ("the Police Station - Civil Lines, Delhi which culminated in the impugned FIR") arriving in the electoral scorer, one product later.
- ⚠ ONE-DIRECTIONAL BY CONSTRUCTION: one edge is split into two tiers, both at or below what was paid before, so this release can never attribute a row the old scorer did not.
- ⚠⚠ THE REGISTER PUBLISHES ITS OWN VERDICT ON WHETHER A NUMBER IS AN EPIC, AND IT WAS BEING HANDED THE STRONGEST ANCHOR ON THE LADDER UNREAD. `epicValid` is on 100% of rows and had zero reads in the codebase. Four of 802 rows in one Assam part are false, and an unreadable EPIC travels with unreadable text beside it: VHAF4SY188722CI / "main pashi c no", 6IIJSY188811A / "salia malaka no", 15IYIWDY031D66 / "lakhi moni f( no". A string that is not an EPIC cannot be THE elector's EPIC, so it may no longer be `hard`, may not win the identification tiebreak and may not confirm unaided; it drops to the fuzzy weight, because the typed number and the scanned number do still agree character for character. The identity card and the limitations say so, and both page payloads now carry the flag.
- ⚠ Only an explicit `false` counts - an absent flag is not a failed check. ⚠ AND WHAT WAS NOT FOUND IS RECORDED TOO: across all five parts, ZERO EPIC strings are carried by two different name/relative pairs, so there is no evidence that grouping by EPIC merges strangers. The harm was the confidence claim, not the grouping.
- ⚠⚠ THE REGISTER NAMES THE ADDRESS BLOCK ITSELF, AND e1.8.3 SPENT A RELEASE INFERRING IT. `sectionNo` is on 96.3% of rows and was read nowhere. A roll part is printed section by section - a street, a hamlet, an apartment block. Measured on the very part e1.8.3 was written about, MH AC 201 part 96, house string `Tandulwadi`: 53 rows carry it, serials 65 to 1247, across SIX sections - and section 1, "Ada ale Basti Tadulavadi", is exactly the 16 rows at serials 65-90 that the serial-gap rule recovers. The register gives that block a name.
- ⚠ IT IS A BOUNDARY, NOT A REPLACEMENT, AND THE MEASUREMENT IS WHY. Delhi part 134 is ONE section for 1,430 electors and Odisha part 6 has two, one of which spans serials 1-850: where the extractor found a single section, grouping by it is worth nothing at all. So a block may not SPAN two sections and everything else is still decided by the serial gap, a missing section on either side splits nothing, and the search log now names the section the subject's own block was printed under.
- ⚠⚠ THE REGISTER SPELLCHECKS ITS OWN NAME DICTIONARY AND ONLY ANSWERS ON A ZERO, AND THE ANSWER WAS BEING THROWN AWAY. `suggestions` has been normalised in electoral-api.js since e1.2.0 and consumed in exactly zero places. Measured: `name=Qxzvrt Blmqwx` returns 0 persons and suggestions ["kasabrt"]; a name that matches returns none. /search's empty state - the one state it had nothing to say about - now offers them, and so does a report that screened nobody. ⚠ Offered as the register's guess and never applied as our correction: the dictionary is folded names and the suggestions are rough. ⚠ Only on a clean zero, or readers learn to skim past them (the e1.7.7 rule). ⚠ And kept PER NAME ASKED, because a zero from the relative-pair filter carries suggestions about the RELATIVE'S name, and offering those as respellings of the subject answers a question nobody asked.
- ⚠⚠ A PUBLISHED FILTER WITH ZERO USES ANYWHERE: `excludeFlagged`. `partFlags` is on 15.1% of the audited rows and the report has read it since e1.7.x, but /voters read nothing. Measured on Assam AC 113 Haflong: 161,778 electors with the filter off and ZERO with it on - every roll page of that seat is flagged, and the page was publishing its community shares, its biggest households, its oldest voters and its parent rankings off rows the extraction itself distrusts, with nothing on the page saying so. The share is now measured in one extra call and stated; the analysis cards ask for the unflagged pages and say which population they read; and if excluding them leaves nothing, the sample is taken again without the filter and the page admits it.
- ⚠ IT NEVER TOUCHES A COUNT. A seat reported as empty because its pages are flagged is the electoral form of publishing CLEAR on nothing screened. ⚠ And an unmeasured share prints nothing - a silent second call must never read as a clean area (the e1.6.6 rule).
- ⚠ `nameAlts` IS EMITTED ONLY ON INDIC-SCRIPT ROLLS - measured, ORI 51.3% of rows, HIN 84.7%, and 0.0% on all three English rolls. It is the transliterator's own alternate readings, it was read by the scorer and shown by neither page, and e1.7.7's whole finding was that a reader needs telling how the roll spells the name they typed. It is on the search rows now.
- ⚠⚠ THREE THINGS THIS FILE AND CLAUDE.md HAD BEEN ASSERTING ARE WRONG, AND MEASUREMENT SAYS SO. (1) An unpublished FACET is SILENTLY DROPPED, not rejected: `facets=gender,partNumber,houseNorm,sectionNo` returns only gender, with no error - and the state-scope attempt cost 23.5 seconds to return `facets: {}`. That is the `litigant=` failure shape, a request that looks like it worked. (2) There is no sort, and `serial` carrying docValues does not create one: sortBy, sort and orderBy all leave the first five serials at [2, 3, 19, 24, 32]. (3) `sectionNo`, `houseNorm`, `pollingStationNo`, `village`, `pcNumber` and `epicValid` are not FILTERS - all six were passed and all six left the total at 1,430. THE SCHEMA IS NOT A CAPABILITY LIST: a field being indexed with docValues says what Solr could do, not what the API exposes.
- Gates at e1.9.0: electoral-calibration 243 (was 226) - locality tiering, epicValid, the section boundary, the per-name suggestion store and the flagged reading, each confirmed to FAIL when its fix is reverted - electoral-voters 544 (was 534), electoral-page (the card caveat, the suggestion line and the row payloads, all reverted and confirmed red), court calibration 658 unmoved, models 92, model-lock green, fixtures 3/3, role-render, api-json.
2026-08-28
e1.8.3, A house string is not an address; the block of serials is
- Findings: FINDINGS-electoral-e1.8.0.md (Grok) - a 314-person panel across 32 states, 258 hit, 3 wrong, all three published probable, 98.9% precision among identifications. Its one substantive open item is closed here.
- ⚠⚠ e1.8.0 WALKED OUT FROM THE SUBJECT'S OWN SERIAL, WHICH WAS RIGHT, AND THEN LET EACH ADDRESS IT PASSED CONTRIBUTE EVERY ELECTOR IN THE PART CARRYING THAT HOUSE STRING, WHICH IS WRONG WHEREVER THE STRING RECURS. Measured on Baramati part 96, Arman at serial 89: "TBDUUIWBUI" is 5 people spread over serials 78 to 776, "TANDULWADIPOAD" is 2 people at 90 and 940, "16" is 3 people at 93 and 1156 - so the card said five people live one door away and put one of them seven hundred rows down the roll. His own house, the village name "Tandulwadi", is written against 47 electors across the whole part, and that was published as his household.
- An address is now a BLOCK: one run of a house key whose consecutive serials stay within SAME_HOUSE_GAP. Arman's household is the stretch at serials 65-89 and his neighbours are 7 electors across 6 addresses, where they were 15 reaching serial 940. Sweety's neighbours fall from 48 electors to 8, the 40 lost being WZ417 and WZ109 rows elsewhere in the part.
- ⚠ STRICT CONTIGUITY IS TOO STRICT AND WOULD HAVE COST THE GOLDEN, SO THE SPLIT POINT WAS MEASURED. Over three whole parts in three states, 1,966 same-key adjacencies: 74.6% sit at a gap of 1, 84% at a gap of 4 or less, and the curve then FLATTENS - 4 to 12 buys 2.4 points - into a long tail that is the different-house-same-string population. Odisha house "217" is one house written across serials 431-433 and 435-449; house "16" is two houses, at 10-13 and 820-829. SAME_HOUSE_GAP is the knee of that distribution and not a guess.
- ⚠ AND THE ODISHA GOLDEN IS UNTOUCHED, WHICH IS HOW THE NUMBER WAS CHOSEN: every one of Sanjay's six neighbouring houses is a single block identical to its whole string (7, 4, 3, 4, 2, 6 people), so the 16-person family and the 26 electors either side both survive.
- ⚠⚠ THE BLOCK ID IS ASSIGNED AFTER THE SORT, AND THAT ORDERING IS LOAD-BEARING. Numbering blocks as they are built and then sorting the array leaves `blocks[blockOf.get(i)]` addressing a different block. That shipped locally for exactly one run and took the golden from 16 people to 10. ⚠ The gate for it has to INTERLEAVE two keys - blocks are built in order of each key's first appearance, so a key's first block is always already in place and a fixture without a second block cannot see the bug at all. The first version of that test passed against the broken code.
- ⚠ WHERE THE HOUSE BOX HOLDS A LOCALITY, THE PAGE SAYS SO. The rows really are beside the subject, but "the house either side" would overstate what the register knows when the string is a village name written against nine separate stretches of one part. Both the report and the search log now name the number of stretches and state that only the subject's own is read as their address.
- Gates: electoral-calibration 226 (was 220), electoral-voters 534, electoral-page, court calibration 658, models 92, fixtures 3/3. All three new gates were confirmed to fail when their fixes were reverted.
- Unchanged from the panel and still open: one-token given names are refused rather than guessed (Bangalore `srinivasagowda` picked a namesake at 77%, probable, never confirmed), and national sweeps for one-token common names time out on Delhi and Tamil Nadu - the Worker raises an error rather than publishing a person, which is the right failure, but 23 of 314 reports did not exist.
2026-08-28
e1.8.2, A cron that is not enabled looks exactly like one that is
- ⚠⚠ e1.8.1 PUT THE SEAT SWEEP ON A CRON AND TWO SCHEDULED BOUNDARIES PASSED WITH ZERO ROWS WRITTEN. The trigger is declared in wrangler.jsonc and the deploy landed - the version endpoint moved - so either Workers Builds does not apply cron triggers on this project or they need enabling on the Worker itself. Verified by watching el_ac_counts: 2,540 seats seeded, newest timestamp unmoved across two boundaries. This is the same failure as every other one in this line of releases: something that looks done and quietly is not.
- ⚠ SO THE TABLE CONVERGES WITHOUT THE CRON TOO. Where no state is due a re-probe, a coverage request counts six constituencies in the background instead - one round of calls, inside the same head start everything else uses. ⚠ EITHER THE STATE PROBES OR THE SEATS, NEVER BOTH IN ONE REQUEST: three state probes at 25s already fill the window, and adding seats on top is exactly how the e1.6.5 batch came to write nothing.
- ⚠ AND THERE IS A WAY TO FORCE IT AND SEE WHAT IT DID. GET or POST /api/electoral/sweep runs a bounded batch and reports measured, failed and queued. A cron that is not enabled is indistinguishable from one that is until the numbers stop moving; this is how you tell them apart. Capped at 60 seats a call, which is cheaper than one visit to the voters page.
- The cron stays declared, because it is the right shape: about 2,400 seats is a job of minutes and the runtime stops background work roughly half a minute after a response. If it is enabled it does the work and the page-side batch finds nothing stale to do.
2026-08-28
e1.8.1, The count follows the register down to the constituency, and moves off the page load
- ⚠️⚠️ THE SCOPE THAT ANSWERS KEEPS GETTING SMALLER AND THE COUNT HAD STOPPED FOLLOWING IT. State scope was the counting primitive at e1.3.0; by e1.6.4 sixteen states no longer answered and districts became the fallback. Measured 2026-08-28, one day after that fallback was built: Patna and Darbhanga - districts that answered the previous afternoon - both fail at the register's own sixty-second gateway, and Rajasthan went from answering at state scope in 12.7s to not answering at all. Everything above roughly a million rows has stopped aggregating. A constituency still answers in four to thirteen seconds, and it is the finest scope the register exposes above a polling part.
- ⚠️ THE RUNG IS NOT A DIFFERENT METHOD, IT IS THE SAME QUESTION ASKED OF A SMALLER SET, and that was verified before it was built: q=<state> plus acNumber returns 243,463 for Jahazpur AC 182 either way, and Arunachal's sixty seats sum to 861,370 against a state-scope 860,124 - a 0.14% gap which is the index growing during the sweep. A successful count is accurate; there simply are fewer and fewer scopes that succeed.
- ⚠️⚠️ THE SEAT LIST IS A CONSTANT, AND NEITHER OF THE TWO OBVIOUS SOURCES WOULD DO. The pinned boundary shapefile is the 2008 delimitation and has no polygons at all for Gujarat's ACs 45 to 64, so walking it drops twenty constituencies from a state total with nothing saying so. And discovering the end of a state by walking until the counts stop reads a partial ingest as the end of the state: Uttar Pradesh held 97 of its 403 seats one day and all 403 the next. A bound a partial load can move is not a bound. AC_SEATS is the delimitation in force; a state absent from it is never counted this way, which is the safe failure.
- ⚠️⚠️ AND THE SWEEP MOVED OFF THE PAGE LOAD, WHICH IS THE PART THAT COULD NOT BE PATCHED. Coverage measured itself inside the request that drew the map, using whatever the runtime allows after a response - about half a minute. That was enough while a whole state answered in twelve seconds. There are about 2,400 constituencies; at six lanes and eight seconds each that is a job of minutes. A cron trigger every fifteen minutes now takes the stalest constituencies in the country and the page only reads. The e1.6.4 note in this file said a longer gateway would buy nothing until the sweep left the request; this is that.
- ⚠️ ONE QUEUE ACROSS ALL STATES, NOT A ROTATION THROUGH THEM - a rotation is exactly what never reached the tail of its own list at e1.6.4, and asking which constituency in India is stalest cannot have a tail. Never-measured outranks merely-old, because a state that has just entered the index is the one whose number is most wrong.
- ⚠️ Each seat is written the moment it answers and a failed count never overwrites a good one - the e1.6.5 rule, one table further down. A constituency that timed out today is not a constituency that emptied.
- ⚠️ A CONSTITUENCY SUM IS A FLOOR AND SAYS SO EVEN WHEN THE STATE IS COMPLETE, because seats not yet counted contribute nothing. The map reads "at least N voters, X of Y constituencies counted so far".
- Measured and seeded this release: the index holds at least 384,673,226 people, against the 127,072,103 the page reported this morning and the 1,592,768 it reported two days ago. Uttar Pradesh went from 445,324 to 47,908,253 in a day, Rajasthan from 1,897,262 to 51,640,532, Karnataka from 11,072,960 to 54,828,089.
- Gates: electoral-voters 534 (was 524) - the queue order, never-measured first, the bounded batch, the seat list refusing both the shapefile and the walk, per-seat writes, and a sweep where nothing answers leaving every stored count intact.
2026-08-28
e1.8.0, A typed voter ID is the identification, and next door is measured from the subject
- Findings: FINDINGS-electoral-e1.7.9.md (Grok). Two of the three faults were introduced by e1.7.8 and are fixed here; the third is older and is the more serious.
- ⚠⚠ AN EXACT VOTER ID WAS LOSING A TIEBREAK TO A NAMESAKE'S LONGER STORY. Measured live: `Sweety Kumari`, father `Suresh Kumar Singh`, submitted WITH epic=TGI8455461. The EPIC lookup ran, the row was screened and scored confirmed, identifying, epicExact, LR 5000 - and the report published NSF4312369, a `sweetee` in Karawal Nagar, because both rows clamp to 99 and both are hard, so `rankScored` fell through to its next test: the LENGTH OF THE TRACE. The namesake had more household story to tell.
- ⚠ AND THE AMBIGUITY EXEMPTION COULD NEVER FIRE, because it asks `!top.epicExact` and the EPIC row was never the top. The report then told the reader that "a voter ID number would settle it" about the voter ID they had already typed. `rankScored` now puts an epicExact row first, BELOW the score so it breaks a tie rather than overriding the posterior. Live: the same form now returns TGI8455461 at 99% confirmed, not ambiguous.
- ⚠ A LIMITATION MAY NOT ASK FOR WHAT THE FORM ALREADY CARRIED. The ambiguity sentence offered "a voter ID number, an exact age, or a house number" regardless of what was given; it now names only what is missing, and where everything was given it says so instead.
- ⚠⚠ AND THE NEIGHBOUR WINDOW WAS MEASURED FROM THE HOUSE STRING'S FIRST APPEARANCE, NOT FROM THE SUBJECT. e1.7.8 ordered a part's ADDRESSES by the first serial each house string appears at and took three groups either side - the same thing only while the string is unique. Venkateshappa, house `W`, serial 517: `W` first appears near position 47, so the six neighbours published were serials 254 to 275, some 250 rows away. Arman, house `Tandulwadi` (a village name), serial 89: `Dada Patil Nagar` was published as two doors away on the strength of its own first serial while its thirteen residents sit at 63, 711, 1023, 1046 and 1099.
- The walk now starts at the subject's own serial and collects the addresses it passes. Live, Arman's neighbours are serials 74, 76, 78, 84, 90, 92 and 93 around his 89. This also makes the section honest where the key is a locality or a single letter: those rows really are next to him, whatever the register wrote in the house box.
- ⚠ THE SERIALS DECIDE WHICH ADDRESSES ARE NEXT DOOR; THE ADDRESS THEN CONTRIBUTES ALL OF ITS RESIDENTS. Collecting only the rows walked past cost the Odisha golden four relatives - 16 people to 12, because house 176 gave the two neighbours either side of the walk rather than the seven who live there, and the grandmother and uncles are the whole reason the section exists. Caught by re-running the golden, restored to 16.
- ⚠ READING A WHOLE ROLL PART IS NOT A HOUSEHOLD CALL. Every page of a part enumeration reported "1,042 electors are registered at that address" about a booth of 618 addresses - the household template, reused. The part enumeration has its own phase and its own sentence now.
- Gates: electoral-calibration 220 (was 218), electoral-voters 524, electoral-page, court calibration 658, models 92, fixtures 3/3. The epicExact tiebreak was confirmed to fail when removed.
- ⚠ STILL OPEN, from the same findings file: `pollingStation` publishes the Pune transliterator artefact `starling` on a Tripura report and Odia roll chrome on Sanjay - `isRollChrome` recognises headings by what they say and neither of those is a heading it knows. And a stored report is not re-stamped by a release, so the changelog's own EC-TMCEGNZ link still serves the e1.7.7 run; cite a fresh code or re-run the subject.
2026-08-28
e1.7.9, A family lives in several doors, and a name that lost its space
- ⚠⚠ A NAME THE SCANNER RAN TOGETHER COMPARED AGAINST ITS OWN SPACED FORM AT NOTHING. On EC-TMCEGNZ the subject's row names her husband `subramanianpancuapakesan`; his own row reads `subramanian panchapakesan`; he is registered in her household with an EPIC of his own, and the tree drew him as a person with no roll entry, because token-by-token comparison scores that pair at 0.60, a slip, under every gate.
- ⚠⚠ THE THRESHOLD IS MEASURED, NOT CHOSEN. Every single-token name was compared against every multi-token name in five whole parts in five states: 87,593 pairs. At 0.97 on the phonetic fold of the squashed forms there are NINE joins and all nine are one name written with or without a space, venkateshwarlupelolla/venkateswarlu pelolla, sudharshanwadhwa/sudarshan wadhwa, raghunandan/raghu nandan, subramanianpancuapakesan/subramanian panchapakesan. The first WRONG joins sit at 0.9667: venkatesham against venkatesh seelam, satyanarayanamurtey against sathyanarayan areddy. The cliff is that sharp. A length guard was tried and does not separate them.
- ⚠⚠ AND ONE FLAT WRITTEN TWO WAYS WAS TWO HOUSEHOLDS. `buildScopes` keyed on the house exactly as printed, so `HOUSENO4083` and `HOUSE NO. 4083`, one serial apart, husband and wife, sat in different scopes and neither could ever resolve against the other. The scope key now collapses case, spaces, dots and hyphens.
- ⚠ THE SLASH IS KEPT, WHICH `houseKey` DOES NOT DO. Over 2,226 addresses in those five parts, stripping every non-alphanumeric merges 12/1 with 121, 13/1 with 131 and 56/9 with 569, survey number and subdivision against a plain plot number, which are different houses in a Telangana or Maharashtra village. Keeping the slash gives 154 merged groups and not one mixes a slash form with a non-slash form, while still joining WZ106 / WZ-106 / WZ 106 / W z-106 as the single flat they are.
- ⚠⚠ AND A FAMILY IN INDIA IS OFTEN SEVERAL DOORS, NOT ONE. A married son next to his father, a brother in the adjoining flat, an uncle three houses down: the register files them as separate households, and until now the relative each named could only ever become a ghost, because the search stopped at the household boundary, even though mergeAdjacentGhosts has always known the next house is family. Measured over four whole parts in four states, 4,292 electors: same-household resolution places 1,095 relatives, and looking three addresses either way places 366 more, a third again.
- ⚠⚠ THE GUARD IS WHAT MAKES IT SAFE, BECAUSE A HOUSE BOUNDARY IS EVIDENCE AND CROSSING IT SPENDS SOME. Inside a household the surname is free and the given name does the work; next door the relative's name has to carry the link alone. So it must be more than one word, or the two electors must share a family name somewhere in their own names. That keeps 366 of 386 candidate links and refuses 20, precisely the doubtful ones: `naresh sanvally` naming `jangaiah` and reaching `seelam anjaiah`, `molagalla anthamma` naming `antaiah` and reaching `medipalli gandaiah`. Different families, one bare given name, two doors apart: the Kerala single-token trap crossing a wall.
- ⚠ The word count is taken BEFORE honorifics are stripped, or `mohd israf`, two words, one token, would have been refused against an exact full-name match on a real neighbour. ⚠ Sex, the parent gap and the spouse gap all still bind, the same household still wins over the house next door, and the link is recorded as `adjacent-household` so the diagram draws it dashed and the table says it was worked out rather than stated.
- Verified live: EC-TMCEGNZ's husband is a real elector rather than a ghost, and the tree goes from 2 people to 3. A Dwarka subject whose husband lives one door away goes from a ghost husband to a family of four. The Odisha golden is UNCHANGED at 16 people across 3 generations, the case with known truth does not move, which is the point.
- Gates: electoral-calibration 218 (was 207), electoral-voters 524, electoral-page, court calibration 658, models 92, fixtures 3/3. The bare-given-name guard was confirmed to fail when removed.
2026-08-28
e1.7.8, A flat number is not a position in a sequence
- Asked why two live Dwarka reports show no houses either side. They did look. EC-TMCEGNZ asked the register for HOUSE NO. 4082, 4084, 4081, 4085, 4080 and 4086; EC-Y97BSKE asked for R5 J5A- 496- Shree Radhev Apartments through R9. Twelve billed household calls, twelve zeros, and a page that then said the neighbours could not be read.
- ⚠️⚠️ WALKING THE HOUSE NUMBER CANNOT WORK IN A CITY. The house strings those two parts actually hold are J082, 1012, 10J2, 1021. Joy Apartment, FNO3092, FLATNO07A, 2nd floor H.no. 54, 40/16B2, 301/A, RZ-228/2. 4TH FLOOR, WZ417, PLOTNO38. A flat identifier is not a position in a sequence and no amount of incrementing reaches the next door. Nothing of the form HOUSE NO. n+/-1 exists anywhere in part 134.
- ⚠️ AND houseNumeric TOOK THE WRONG DIGIT RUN ANYWAY. On R6 J5A- 496- Shree Radhev Apartments it returns 6 - the digit inside the block letter R6 - not 496. That walk was wrong in principle as well as in practice.
- ⚠️ THE JUSTIFICATION WRITTEN OVER pullNeighbours WAS ALWAYS THE RIGHT ONE AND WAS NEVER USED: parts are printed house by house and SERIALS run in address order. The part is now read in full and its addresses ordered by the serial each first appears at, which is the register's own ordering, needs no arithmetic on the house string, and works identically for 177 in an Odisha village and RZ-228/2. 4TH FLOOR in Dwarka.
- Live, the two reports that had nothing: EC-TMCEGNZ now returns six neighbouring addresses (H NO. 4012. Joy Apartment, 2081IOYBQIISSOC2, FNO3092 with four residents, FLATNO07A, FNO3072/7TH/F, 2nd floor H.no. 54) and EC-Y97BSKE three. The Odisha golden is unchanged and slightly better: houses 174 to 180 with all their residents, and the 16-person family across three generations intact.
- ⚠️ Addresses are grouped on the normalised house key, so WZ417 / WZ 417 and HOUSE NO. 4083 / HOUSENO4083 are one address. ⚠️ THAT IS ROBUSTNESS, NOT A DEMONSTRATED FIX. It was first written up here as "the exact household filter misses the co-resident under the other spelling", and checked live it does not: householdHouse=HOUSE NO. 4083 returns both Panchapakesans. The claim was removed rather than shipped.
- ⚠️ A PART IS ABOUT A THOUSAND ELECTORS, SO THIS IS ELEVEN PAGES RATHER THAN ONE CALL. It runs only where the budget carries it and the part is under 3,000; above that, or where the enumeration returns nothing, the house walk stays as the fallback and the honest empty message stays after that.
- housesInRollOrder is pure and gated: ordering by first serial rather than by the house string, one flat written two ways counted once, a person counted once however many occurrences they carry, another part's rows excluded, and a village part ordering exactly as the old walk did.
- Gates: electoral-calibration 207 (was 202), electoral-voters 524, electoral-page, court calibration 658, models 92, fixtures 3/3.
2026-08-28
e1.7.7, Fifteen electors, none of them spelled the way you asked
- Founder asked why /search?name=sajat+jain&match=phrase shows Sajata and not Sajat. Measured live: phrase returns 15 persons, 13 labelled nameFolded reading `sajata jain` and 2 labelled namePhoneticOnly reading `sajata jeena`. ZERO are nameLatinExact. The register folds a trailing schwa, so sajat and sajata are one name to it - and it publishes the fold in the other direction on those same rows, as nameAlts `sajata jaina`. The page was right about every row and silent about the finding.
- ⚠️⚠️ A RESULT SET WITH NO EXACTLY-SPELLED ROW IN IT MUST SAY SO OUT LOUD. The per-row matchedOn badges have carried this since e1.2.4, one row at a time, in nine-point uppercase - and nobody adds up fifteen badges. `spellingReading()` counts the register's own verdicts over the rows in hand and the page states it once, above the table: nobody here is spelled the way you typed it, here is how they ARE spelled, with the counts.
- ⚠️ THE REGISTER'S VERDICT IS THE MEASUREMENT, NOT OUR STRING COMPARISON. matchedOn is the index saying which bucket it matched a row in; it knows which folds it applies and that it applied them. The Latin spellings are read off the rows only to NAME what came back, never to decide whether a row was exact.
- ⚠️⚠️ AN UNMEASURED SET IS NOT A SET WITH NO EXACT MATCH. Where no row carries a name* verdict the reading is `known: false` and the page says nothing at all. Treating the register's silence as a counted zero is eCI-1.1 (v7.8.1) arriving in the second product - a search that succeeds and returns nothing is not a measurement - and the gate fails if that guard is removed.
- ⚠️ IT IS A READING OF ONE PAGE AND IS LABELLED AS ONE. There is no exact-match mode on this API to count a whole result set with, so the sentence says `of the 25 shown on this page` whenever the set is larger. A claim about 641 rows made from 25 of them would be the fault this fixes, inverted.
- ⚠️ IT FIRES ONLY ON A CLEAN ZERO. A page holding even one exactly-spelled elector needs no explanation, and printing the note beside a real exact match would train readers to skim past it on the day it matters.
- ⚠️⚠️ AND A NOTE IN THIS FILE WAS WRONG, WHICH IS WHY THE BUG COULD HIDE. The header comment said nameMatchMode=phrase returns nameLatinExact and nameFolded `and none of the third`. It does not: 2 of the 15 here are namePhoneticOnly. Phrase is a narrower question, not an exact one, so the strict count behind the commonness band is a smaller number rather than a clean one. Corrected where it was written, and the headline keeps saying `carry it or a spelling of it` for exactly that reason.
- Gates: electoral-page +11, both faults confirmed to fail the suite when reintroduced (known forced true; the exact>0 guard removed). electoral-calibration 202, electoral-voters 524, court calibration 658, models 92, model-lock green - all unmoved, nothing here touches a scorer.
2026-08-27
e1.7.6, A note only an API reader ever sees is not a fix
- ⚠️⚠️ EVERY ITEM IN THIS RELEASE SHIPPED CORRECT IN THE JSON AND WRONG ON THE SCREEN. e1.7.4 wrote the honest sentence for each of them and then left it in the payload, which is its own failure mode: it reads as done in a diff, it passes an API-shaped gate, and the reader sees the old page. The gates for these are on the rendered page now, not on the object.
- ⚠️⚠️ THE COMPLETENESS NOTE NEVER REACHED THE HEADLINE. Jahazpur's reading is 18 of 18 sampled parts present, which is NOT the claim that the seat is loaded - its booths account for 50,774 of 243,463 voters and the sweep sees part 261. kpi() painted only partial_roll, so the headline went on saying 243,463 voters on the roll: the e1.4.7 sentence the whole field exists to stop. It is printed whenever there is a reading, partial or not.
- ⚠️ ZERO AT THE START IS NOT A HOLE IN THE MIDDLE. Kasganj AC 100 probes 0 of 12 on a seat holding 5,637 people against a real electorate of a lakh-plus, and the copy told the reader the gaps were in the middle of the sequence - describing a different thing entirely. A seat whose sample came back empty gets its own sentence.
- ⚠️⚠️ WHERE THE ROLL NAMES NO BUILDING, THE STATIONS CARD WAS THE BOOTH LIST A SECOND TIME. e1.7.4 closed the station DROPDOWN on station_names_absent and left the card, so Jahazpur still rendered sixty rows reading Booth 32, Booth 21, Booth 33 under a heading promising that each station is a building holding several booths - with the booth card directly underneath showing the same sixty parts. One sentence is the honest version of that card on such a roll.
- ⚠️⚠️ `starling` LEFT THE STATION GROUPING AND STAYED ON THE BOOTHS TABLE. Baramati's stations came back as fifty honest Booth N plus ten real school names and zero starling, while booths[].station kept the rejected string and the table printed it. Only groupStations had dropped the name. One predicate, stationUsable, is now the single definition and the row is cleared through it - stationRaw still carries what the register said, for provenance. A name this code has decided is not a name has to be gone from every surface, not from the one that was measured.
- ⚠️ A CARD'S NOTE IS WRITTEN BEFORE ITS DATA ARRIVES, SO IT CANNOT NAME THE ORDER - AND IT WAS NAMING IT ANYWAY. The voters card promised the roll's own order over a page that, above the 3,000 sort ceiling, is the register's relevance ranking: Sadar Bazar page one starts at serials 6, 9, 51, 63. The heading is neutral now and rowsPanel, which knows which of the two it got, says so in the body.
- Deliberately not done, and the QC agrees: the stub seat Sahara AC 179 at 30,698 beside neighbours of 240k-plus, and the null AC names, are ingest and client-backfill rather than this release. The probable area mix saying which population it was taken from is real and is left for the release that owns that card.
- Gates: electoral-voters 524 (was 520), three of them asserted against the rendered page rather than the payload, plus the partial sentence no longer claiming it only looked at the start of the seat.
2026-08-27
e1.7.5, The youngest voters, and why that list needs a floor
- Founder's ask: the notable card shows the oldest voters here, so show the youngest too. Same shape - name, house, age, and a link that opens that voter's own report.
- ⚠️ THE YOUNG END NEEDS A FLOOR EXACTLY AS THE OLD END NEEDS ITS 115 CEILING. The roll's age field is OCR and it misreads in both directions: at one end it prints 130-year-olds, at the other it prints single digits. A "youngest voter, aged 4" is the mirror image of the impossible centenarian and would be quoted just as readily. The list runs 18 to 25.
- ⚠️ 18 IS THE FLOOR EVEN THOUGH THE ROLL LEGITIMATELY PRINTS LOWER, and the copy says why rather than leaving a reader to assume a scandal. India has allowed advance registration at 17 since 2022, and every row prints the age recorded at ITS OWN revision rather than today's, so a number below 18 is often real and never means an under-age person is voting. Those rows are counted on the card instead of listed under a heading that says voters.
- ⚠️⚠️ AND THE TIE-BREAK IS DETERMINISTIC, WHICH MATTERS MORE AT THIS END THAN AT THE OTHER. There is usually one 104-year-old in a booth and there are hundreds of 18-year-olds, so without a stated order "the youngest voters" is whichever ten the sample happened to hold, in whatever order it held them - the register's relevance ranking published as a finding, which is exactly the e1.7.0 lesson. Age, then booth, then serial: the roll's own order.
- ⚠️ THE BAR IS DRAWN AGAINST THE LARGEST VALUE IN THE LIST, AND THIS IS THE FIRST LIST THAT ASCENDS. The helper took the first row as the maximum, which is right for every descending list on the card and makes every later row on this one wider than its own track.
- Gates: electoral-voters 520 - the floor, the ceiling, ascending order, the deterministic tie-break, the counts of first-time voters and of unusable ages, and the standing invariant that no community label reaches a named elector.
2026-08-27
e1.7.4, One function was fixed and the other still painted
- ⚠️⚠️ THE UNEVEN-LOAD FLAG NEVER FIRED ON THE TWO STATES IT WAS WRITTEN FOR. unevenlyLoaded reads districts_seen and top_share off the coverage row, and only the COVERAGE probe wrote them - but on Rajasthan and Maharashtra the state-scope count is the slow call and the GEO drill is the one that answers, so those two rows never got a first value and COALESCE cannot invent one. The map went on saying Rajasthan 1,897,262 voters while 64.7% of that is Bhilwara. part=geo now writes the shape from the breakdown it already holds, and the existing rows were backfilled from a measured sweep: Assam 0.91, Maharashtra 0.81, Dadra and Nagar Haveli 0.68, Rajasthan 0.63, J&K 0.60, against Uttar Pradesh 0.05 and Arunachal 0.11.
- ⚠️⚠️ THE GIVEN-NAME VETO WAS APPLIED IN ONE OF THE TWO PLACES THAT PAINT A COMMUNITY. classifyRow refuses an out-of-region map hit on a token that is a given name in this roll, which is why the Meena booth's chart lost its fifteen phantom Yadavs; surnameRanking went on publishing jati: yadav on gopal into the JSON, below the fourteen rows the page prints, where nobody saw it and any importer did. Same narrow test in both now: given-name-here AND unattested-here. Verified both ways - gopal in S20 carries no caste, gopal in S18 still reads Yadav.
- ⚠️⚠️ `starling` SURVIVED ITS OWN GUARD BY BEING EXACTLY EIGHT LETTERS LONG. STATION_MIN_CHARS is 8, so the Pune transliteration passed the length rule; the parts-count rule then stopped the fifty-booth mega-station but left all fifty booths wearing the name. A real polling station is written as a place - gulabi bagh, sarvodaya bal vidyalaya - and one Latin word with no separator in it is a transliteration artefact. ⚠️ And a name we have decided is not a name must not be the label either: the loose fallback read b.station || Booth N, so the rejected string was carried straight down onto the booth it had just been rejected for.
- ⚠️ THE ROW PAYLOAD STILL CARRIED THE PDF HEADING. Grouping went through stationOf and publicRow did not, so the HTML table was clean - it has no station column - while anyone reading the JSON got आरंभिक क्रम as somebody's polling station. That is the worse of the two directions to get wrong.
- ⚠️ THE HOUSE TEST WAS ON THE HOUSEHOLD LIST ONLY. looksLikeHouse kept OCR glyphs out of biggest-households and the family join never called it, so three people sharing house ययफ - three Devanagari letters, no digit - were still published as a family. The 0.6 house-share join is right; the key it joins on has to be a house.
- ⚠️ WHERE THE ROLL NAMES NO BUILDING THERE ARE NOT TWO PICKERS. Once the headings were recognised as chrome every station became a bare booth number, so the station select listed the same sixty rows as the booth select beside it under a label promising a building. station_names_absent was computed at e1.7.0 and never read; the select now says the roll does not name its polling stations, and stops.
- ⚠️ `partial: false` IS NOT "THIS SEAT IS LOADED", and it was being read as one. Jahazpur returned 18 of 18 present while the booth sweep accounted for 50,774 of its 243,463 voters: eighteen points across a range of about 270 parts can all be green and still sit either side of a hole. The reading now carries the word for what it is - sampled-parts-present, or holes-in-the-part-sequence - and the summary carries a sentence saying a sample of the part range is not a count of the seat.
- ⚠️ THE VOTER LIST NAMES ITS OWN ORDER. The card says "as printed on the roll", which is true at booth scope and false above the 3,000 sort ceiling, where page one of Sadar Bazar starts at serials 6, 9, 51, 63. The list was not wrong; the sentence over it was.
- West Bengal's two lost districts were re-walked and written back: South 24 Parganas 1,036,429 and Kolkata North 1,139,151, taking the state floor from 1,523,548 to 3,699,128.
- Gates: electoral-voters 509. The backtick-in-a-comment-inside-a-template-literal trap was hit again while writing the station-picker comment, for the fifth time; node --check is still the only thing that catches it.
2026-08-27
e1.7.3, The area mix is probable, and the state is the prior
- The religion chart on a Hindi-belt constituency was being drawn only from distinctive markers. Kumar, Singh and Devi were shown as themselves and then left out of the mix, while Mohammad and Pathan still counted, so Kasganj AC 100 published Hindu 59 and Muslim 61 of 2,832 readable names. That is a sampling bias, not a finding.
- Shared last tokens now contribute a regional religion prior to the AREA mix only: Kumar as Hindu; Singh as mostly Sikh in Punjab and Chandigarh, mixed in Haryana and Delhi, mostly Hindu in the Hindi belt. Names with no marker follow that mix, pulled toward the state's usual pattern. classifyRow is unchanged, so a named elector still carries no religion.
- Hindi-belt rolls print कमलादेवी and ज़बरसिंह as one word. A trailing filler is now split, so those land on Devi and Singh rather than in not-recognised. A mapped surname (singhal) and a short remainder (gopal is not go+pal) are left whole.
- The name chart still shows Singh and Kumar as those names. The religion layer is the probable mix. Caste and jati stay on distinctive surnames only: Kumar is not a community.
- Gates: electoral-voters 506 (was 491) - regional priors, glued Hindi-belt suffixes, Singh split by state, Bodo still unestimated on religion. electoral-page, electoral-calibration 202, court calibration 658.
2026-08-27
e1.7.2, A small answer to the wrong question, and a column heading on somebody's report
- Findings: FINDINGS-electoral-e1.7.1.md (Grok), which also ran a 12-person panel drawn from live booths in eight states and scored the engine 10 hits, 0 wrong EPICs, 2 honest refusals. This release closes the P2s it left open.
- ⚠️⚠️ THE INVERSE WAVE STOPPED AT THE FIRST FORM THAT RETURNED A SMALL SET, WHICH IS NOT THE SAME AS FINDING HIM - and the comment above it claimed to go widest-first, so the code and its own explanation disagreed. Scoped to Karnataka the typed `M LINGA MURTY` returns 5 phonetic `Linga Murthy M` namesakes, comfortably under the ceiling, so the wave accepted them, opened five strangers' households and never asked for `LINGA MURTY MANDAVILLE`, which is the spelling the register actually prints. Every form is asked now, most specific first, because the form carrying the subject's own surname is the likeliest to be the man rather than a namesake of his initials.
- ⚠️ THE SEARCH LOG NAMED A STRING THE SEARCH DID NOT SEND. W4 carried the retried relative and interpolated the typed one, so the log read `M LINGA MURTY` about a call that asked for `LINGA MURTY`. That log is the page a reader opens to check what was actually done.
- ⚠️⚠️ A ROLL COLUMN HEADING WAS BEING PUBLISHED AS SOMEBODY'S POLLING STATION. e1.7.0 taught the voters page that `pollingStation` on a Rajasthan SIR roll holds आरंभिक क्रम - the heading of the column the building's name should have come from - and the individual report was never told. A subject in Bhilwara, Valmikinagar or Churah has been shown a piece of PDF furniture as the place they vote. Absent is the honest value, and the part number still locates them.
- ⚠️ AND THE CHROME ARRIVES WITH THE PART NUMBER ON THE FRONT OF IT. The voters page strips that prefix before asking; this path asked bare, so "1 - ..." forms sailed through. Both shapes are tested, and the value kept is the original, because "487 - 48/ - nammura patel ... school" is how the register names a real station and the number is part of it.
- ⚠️⚠️ केंद्र AND केन्द्र ARE ONE WORD AND TWO CODEPOINTS - an anusvara against an explicit na plus halant - and the roll uses both. Himachal Churah part 1 publishes "1 - मदान केन्द्र की संख्या व नाम", which a pattern written with only the anusvara form missed entirely. Sikkim's "no and name of part :" was not recognised either. A heading that is missed is a heading that gets published.
- The chrome predicate is imported, never copied: one definition, in the module where it was measured, already in the bundle, and no cycle.
- Verified live on all four goldens after the change, because the inverse wave is what builds the family: Kartik 93% probable - Sanjay 99% confirmed with 16 people and 15 relatives across 3 generations - Srinivas 98% confirmed, 9 people - Ramesh 94% probable, cap intact. Real station names survive; chrome does not.
- ⚠️ DELIBERATELY NOT DONE, and both are in the findings file. A dotted initial still folds a name into its clan - `n.githa` measures 6,682 Geethas in Karnataka and she is correctly left unidentified at 33% - and a one-token urban given name shared with thousands of husbands called Anil is refused rather than guessed. Both are recall holes on names the register cannot separate, and changing how an initial is counted is a scoring change that needs its own measurement, not a patch.
- Gates: electoral-calibration 202 (was 199), electoral-voters 491, electoral-page, court calibration 658.
2026-08-27
e1.7.1, The measurement landed, and was spent on the wrong population
- ⚠️⚠️ e1.6.7 MADE THE SUBJECT IT WAS WRITTEN FOR WORSE, AND ONLY A LIVE RUN FOUND IT. Teaching the pair COUNT to retry the relative under the spelling the roll prints was right; the pair SEARCH was left asking under the string the form typed. So on EC-HND8EVK the count was taken with LINGA MURTY and the search went out as M LINGA MURTY, the identified row never carried the name+relative provenance tag, and inPairSet - which had no other route - stayed false. The scorer then paid nameHits/(k+1) = 5 instead of the distinctive-father proxy's 400, because the call site reads `paired || relativeEdge(...)` and a non-null return DISPLACES the fallback. Identity fell 99% confirmed to 87% probable, and the card said "this entry is not among them" about the one elector in a set of one, who was him.
- ⚠️ A ROW THE REGISTER'S FILTER DID NOT RETURN NOW GETS NO PAIR EDGE AT ALL, and the commonness proxy runs instead. The k+1 branch existed so we could never claim a unique pair about a man the count excluded; it must not then replace evidence that measures something real with a weaker number and a sentence calling him a stranger.
- ⚠️ AND BEING IN THE SET IS TESTED DIRECTLY, NOT ONLY BY PROVENANCE. A tag applied by one discovery wave is a proxy for the fact; whether this row's own relative matches the query the count was taken under IS the fact. Tested at exact or folded only - the bar the register's own filter uses - because the phonetic band would sweep in the Odisha golden, whose father is taitanya das on the son's row and chaitanya das on his own, and who genuinely is not in that count.
- ⚠️⚠️ ONE CEILING, APPLIED ONCE. The prior is 1/min(20000, nameHits) and the pair edge was min(1500, nameHits/k), so the two could never cancel to 1-in-k and the product's own worked example could not confirm: Srinivas Reddy, 42,452 bearers, EXACTLY ONE elector carrying both names, matched relativeExact - (1/20,000) x 1500 is 13 to 1 AGAINST, where the honest reading is even money. The edge is now weighed against the same population the prior was taken from. Live: 80% probable to 98% confirmed. Ramesh Kumar, 72 sharing the pair on a common name, stays at 94% under the common-name cap, which is the cap doing its job.
- ⚠️⚠️ "OCCUR TOGETHER ONCE, WHICH IS WHAT MAKES THE PAIR IDENTIFYING" WAS PRINTED ABOUT A STRANGER. The observation and the findings blurb both keyed off the COUNT alone, and a count of one says nothing about whether the one is the person in the report. On the Odisha golden that elector is a Delhi namesake. Both surfaces now require the identified elector to be in the sample the filter returned, and say plainly when they are not.
- The inverse wave - the most valuable one there is, and the only one that survives the subject's own name being unreadable - walks the same relative-forms ladder, widest first, stopping at the first form that returns a set small enough to open household by household. It had been asking for the father under a name the roll does not print.
- ⚠️ THE COVERAGE METER WAS FROZEN BEFORE THE DISTRICT EXISTED. probeCoverage runs right after wave 0, where districtFilter is null by construction, so every subject with a good district reported state-present 78 instead of district-present 88, and the district was grounded and counted two steps later without the meter hearing. A positive district count now settles it, upwards only. Live: Sanjay, Srinivas and Ramesh back to 88; Kartik correctly stays at 78, because Bangalore is several districts and groundGeography rightly refuses to mint one from the answer.
- A breakdown of one person is three billed calls restating the row above it. The booth card skips the clan sentence and the gender, age and relation facets when only one elector at the station carries the name.
- Verified live end to end on all four known-good subjects rather than by offline gates alone: Kartik 93% probable (hard, honest pair) - Srinivas 98% confirmed - Sanjay 99% confirmed via household and age, pair correctly disclaimed - Ramesh 94% probable, cap intact.
- ⚠️ KARTIK IS 93 AND NOT 99, AND THAT IS THE HONEST NUMBER. The 99 came from the commonness proxy paying LR 400 because five electors are NAMED M Linga Murty - a population that does not contain his father at all, since the roll writes him Linga Murty Mandaville and no elector carries that string. The measured pair, 1 of 10 name-bearers and it is him, is worth 10. Restoring 99 would mean preferring a proxy over a measurement because the proxy flattered.
- Gates: electoral-calibration 199 (was 191), electoral-voters 491, electoral-page, court calibration 658, models 92, model-lock, fixtures 3/3.
2026-08-27
e1.7.0, A PDF column heading is not a polling station, and a relevance ranking is not the roll
- ⚠️⚠️ THE STATION NAME ON A RAJASTHAN SIR ROLL WAS THE PDF'S OWN COLUMN HEADING. Every row of Bhilwara AC 182 part 51 carries pollingStation = आरंभिक क्रम, Hindi for "initial order"; its sibling मतदान केंद्र की संख्या व नाम is the heading of the column the building's name should have come from. 59 of that seat's 60 stations were the first string, so the dropdown was sixty rows of one word and choosing the station chose one booth with a decorative label. This is the Pune starling trap in another script: the length and parts-count guards only ever caught SHORT LATIN strings, and a long Devanagari heading passes both. Roll chrome is recognised by what it says, treated as ABSENT rather than cleaned up, and groupStations falls through to Booth N, which is true and cannot be mistaken for a school.
- ⚠️⚠️ THE VOTER LIST WAS THE REGISTER'S RELEVANCE RANKING UNDER A HEADING THAT SAYS THE VOTERS. Page one of that booth began at serial 851, then 871, 874, 894, 983, 988, 1007, 47, 55, 74 - serial 1 was not on page one, and households sitting on consecutive serials were scattered across pages. There is no sortBy on this API, so ordering is our job wherever we hold the whole set, and only there: sorting one page of a ranking just reorders an arbitrary slice and hides that it was arbitrary. A scope of 3,000 or fewer is read in full and published in roll order; above that the old behaviour stands and the payload names its own order.
- ⚠️ Number(null) IS 0, NOT NaN, so the first cut sorted every row with no serial to the TOP of the roll. Missing is tested before the coercion now.
- ⚠️⚠️ A COMMON FATHER'S NAME IN ONE BOOTH IS NOT ONE MAN. Parents with most voters here led with nand 13, ramdev 13, harnath 12 - every elector in the part whose father-field OCR'd to an ordinary given name, joined across unrelated houses, offering to open a youngest child aged 85. This is the court engine's single-token-father rule arriving in the electoral product two years late. The test is the HOUSE, not the name's rarity: a rarity measure needs a population this card does not have, while who lives together is right there in the rows.
- ⚠️⚠️ A GIVEN NAME THAT IS A SURNAME SOMEWHERE ELSE MUST NOT PAINT A CASTE. Fifteen electors in a Meena booth were published as YADAV because their last Latin token is gopal, whose map entry is attested in Odisha only; out of region it demotes to low, and low is still kept. gopal is also the sixth commonest FIRST name in that booth, which is the tell. The veto is deliberately narrow - the token must be a given name in THIS roll AND the reading unattested in THIS state - because vetoing on the given-name index alone would destroy the one reading that booth gets right: meena is a first name there too, and the real local community surname. Verified both ways: gopal in S20 yields no Yadav, gopal in S18 still does.
- ⚠️ AN OCR SMUDGE IS NOT A HOUSE. House keys of one or two Devanagari glyphs, and 45J and 4S9, climbed the biggest households list because everything unreadable landed in them. A house number almost always has a digit and a bare pair of glyphs names no dwelling. Such rows still appear on the person's own line, because that is what the roll printed; they are simply not ranked.
- ⚠️⚠️ TWELVE PROBES AT THE START OF A SEAT CANNOT SEE A HOLE AT PART 80. Jahazpur returned 12 of 12 and published as not partial while the booth sweep on the same seat found part 261, so the first twelve booths are present was being read as this constituency is loaded. The probe now also samples across the rest of the range, sized from the seat's own total. And it is a test on the CONSTITUENCY, so it no longer runs on a booth page, where it billed twelve calls to answer a question about a different scope than the number on screen.
- ⚠️ capped MUST MEAN WE STOPPED AT THE CEILING and it meant sampling saw more than sixty: sampling found 16 parts while the rendered list was a 60-row slice of a 261-part seat, so capped was false about a list that had plainly been cut.
- ⚠️⚠️ INDEXED IS NOT COMPLETE, AND THE MAP MADE THEM LOOK LIKE ONE. Rajasthan shades as a finished state at 1.9M while ONE district, Bhilwara, is 1,224,722 of that 1,935,247; Maharashtra is 81% one district and Assam 91% one. A finished state never looks like this - Delhi's largest district is 21% of Delhi, Bihar's 7% of Bihar. A districtCode facet rides free on the coverage probe's existing call, and a state heaped into one district now says so beside its own number. Two districts minimum, because Chandigarh IS one district and 100% there is the truth rather than a warning.
- ⚠️⚠️ AND A FAILED DISTRICT PROBE WAS STILL ERASING A GOOD COUNT - the e1.6.5 lesson in the other table, where the upsert was still n=excluded.n. Proved by doing it by hand: a bulk reload of measured district counts took West Bengal's stored floor from 2,562,053 to 1,523,548, purely because two large districts failed on the day of the reload. A clean zero still overwrites.
- Measured this session by walking every district code of every state that cannot be aggregated at state scope: the index holds at least 114,358,970 people, against the 1.6M this page reported three releases ago. Bihar alone is 27,310,173 across all 38 of its districts and Telangana 22,236,460. 53 districts across Andhra, Karnataka, Tamil Nadu, Telangana, Bengal and Delhi still exceed the register's own 60-second gateway and are not in that figure.
- Gates: electoral-voters 491 (was 463) - roll chrome in both scripts, the station fallback to Booth N, serial ordering including the Number(null) trap, the gopal veto proved in both directions, the uneven-load shape, and the booth page no longer carrying the constituency's ingest test.
2026-08-27
e1.6.7, A filter that returned nothing was published as proof, and three searches could not have returned a row
- Findings: FINDINGS-electoral-qc.md, measured on EC-HND8EVK (Kartik Mandaville s/o M Linga Murty, address "Bangalore"). The identification itself was right; the working around it claimed more than the register had been asked.
- ⚠️⚠️ A FAILED `relativeName` FILTER WAS PUBLISHED AS PROOF THE PAIR IS UNIQUE. `relativePairEdge` has always refused to pay for `pairHits <= 0`, the scorer was never wrong, but the findings blurb said "no elector in the whole index carries both", the observations said "they occur together never, which is what makes the pair identifying", and the search log said "nobody else on the indexed roll matches, which is what makes the detail identifying". Live, the pair exists: `relativeName=M LINGA MURTY` returns 0 and `relativeName=Linga Murty` returns the subject, because the roll writes the father `Linga Murty Mandaville`. This is the court engine's eCI-1.1 fault one product over, a search that succeeds and returns zero is not a measurement of rarity.
- A pair count of nought is now null at source, which routes every surface to the "could not be counted" branch that already existed, and the raw filter result is kept beside it as provenance. The posterior does not move: the scorer was already ignoring the zero.
- ⚠️ AND THE PAIR IS ASKED PROPERLY BEFORE IT IS GIVEN UP ON. `relativeQueryForms` offers the register the shapes it actually prints, drop a leading single-letter initial, and append the subject's surname (the house-name pattern this roll used), and a zero is re-asked in them, at most twice, and only on a zero. ⚠️ Never down to a single token: a one-word relative names a clan, not a man.
- ⚠️⚠️ THE DISTRICT WAS FILTERED ON BEFORE THE REGISTER HAD CONFIRMED IT. `decomposeZ` promotes a city to a district, so an address reading only "Bangalore" became `districtName=BANGALORE`, a name the electoral roll does not use, since it files that city under BANGALORE URBAN, BANGALORE RURAL and the BBMP benches. Measured: `name=M Linga Murty` returns 24 nationally and 0 under that filter, and the inverse wave, the most valuable one there is, reported that nobody of the name was registered there. `groundGeography` had corrected the district two steps later, from rows.
- The district is now two fields: `districtName`, what the reader is told the address says, and `districtFilter`, what may be sent to the register, set only by `groundGeography`, from districts the register itself returned. The coverage probe reads it too, so an ungrounded district can no longer report a loaded roll as absent.
- ⚠️ AND A CITY NAMES ITS STATE BY LOOKUP, NOT BY MODEL. The address parser recognised "Bangalore" and threw the state away, so every wave ran unscoped while the model pass, correctly told not to guess geography, returned nothing. `stateCodeFromCity` is a closed forty-entry table consulted last, after the form's state and the parser's, so it can only fill a blank. It yields a state and deliberately never a district.
- ⚠️⚠️ NEIGHBOURS WERE WALKED BY INCREMENTING OCR. The house on that roll is `22054PFOSIIQOFGFDSROSI`, a failed scan of an apartment identifier; `houseNumeric` read 22054 and the walk asked for `22053PFOSIIQOFGFDSROSI` through `22057…`, six billed calls, six zeros, six household keys that have never existed, and the page then said the houses either side could not be read. `houseIsWalkable` tests shape, not length alone: a real house number is short, or long because a human put a separator in it. The real houses on that page are 97, 126, 133/1 and 13J/J, and all of them still walk.
- ⚠️ AND A FAILED SCAN IS NOT THE FIRST LINE OF SOMEBODY'S ADDRESS. It was printed in bold above `Haraluru`, which is the line a person would write. The address now leads with the locality and reports the register's own value as its own labelled line, because the roll does hold it.
- ⚠️ `pincode` IS A FACET, NOT A FILTER, AND THE SEARCH WAVE SENDING IT IS DELETED. The live capabilities list publishes it under facets and under matchedOn and not under filters; e1.2.0 measured the same thing and removed it from /electoral/roster. An unknown parameter is ignored by this register rather than rejected, so the wave was a billed call returning the unnarrowed national set under a log line reading "registered in PIN code X". The scoring edge against a row's own pincode is real and stays.
- Gated: a report whose pair filter returned nothing may not contain "no elector", "occur together never" or a zero called identifying; and no line in the discovery or pipeline source may carry a search parameter the register does not publish as a filter. Both gates were confirmed to fail when the faults were reintroduced.
- Gates: electoral-calibration 191 (was 178), electoral-voters 463, electoral-page, court calibration 658, models 92, model-lock, fixtures 3/3.
2026-08-27
e1.6.6, An error is not a zero, one layer below where that was written
- ⚠️⚠️ A 200 THAT CARRIES NO COUNT WAS BEING PUBLISHED AS "NO ROLL IN THE INDEX YET". normalizeSearch coerces a missing totalPersons to 0 while leaving ok true, so a throttle or soft-error body is indistinguishable from a state with no voters. Caught live minutes after e1.6.5 shipped: the survey wrote Chandigarh as empty one minute after the identical query returned 323,740 twice in a row, and wrote Chhattisgarh as empty while that query was failing outright at the register's sixty-second gateway. This is the e1.4.0 rule, an error is not a zero, broken one layer below where it was written.
- The response now carries `counted`, saying whether the register actually reported a number, and the voters page's measure() treats a 200 with no count as no answer. ⚠️ The coercion itself STAYS: dozens of callers do `r.totalPersons > 0` and would break on a null, so the truth is added beside it rather than in place of it.
- ⚠️⚠️ AND A ZERO IS ASKED TWICE BEFORE IT IS BELIEVED, AND ONLY A ZERO. Of the three answers this probe can give, empty is the only one that denies the state is in the index at all, and it is the one a throttled call is likeliest to fake. It is also by far the cheapest to re-ask: a genuinely empty state answers in well under a second, so confirming costs nothing on the states that really are empty and rescues the ones that are not. If the second call disagrees or does not answer, nothing is written as empty.
- ⚠️⚠️ AND ONLY A POSITIVE COUNT IS CARRIED FORWARD ON A FAILURE, NEVER A ZERO. e1.6.5's carry-forward preserved a stored zero exactly as it preserved a good count, so a state that once read empty would keep publishing "no roll in the index" through every later failure, reintroducing the fault of e1.6.4 by way of its own fix. Where the last reading was empty and the new probe did not answer, the honest word is unknown.
- Gates: electoral-voters 463 (was 456), a 200 with no count never recorded as empty, a contradicted zero never written, a zero the register gives twice still believed so a real gap still shows as one, and a stale zero never carried.
2026-08-27
e1.6.5, The survey that wrote nothing, and the one that erased its own best numbers
- ⚠️⚠️ e1.6.4 SHIPPED AND WROTE NOTHING, AND ONLY A LIVE CHECK FOUND IT. Eight page loads against the deployed Worker produced zero rows in el_state_coverage. The batch write sat behind `pooled`, so a request's whole work was lost unless EVERY state in it finished, and the runtime stops waitUntil work about half a minute after the response, while one state timing out at forty-five seconds held the batch well past that. Every state is written the moment it answers now. A slow state may cost its own reading; it must never cost anybody else's.
- ⚠️ AND THE PASS IS SIZED TO FINISH: three states per visit, one per lane, so a visit is exactly one round rather than two, and a 25s probe ceiling rather than 45s. Measured state-scope times say what that buys, Rajasthan 12.7s, Andaman 13.3s, DNH and DD 17.2s, Sikkim and Tripura 20.1s, Arunachal 20.5s, Maharashtra 22.4s, Himachal 23.0s are inside it; Mizoram 37.6s, Nagaland 52.7s and Goa 54.0s are not, and are left to the district-probe path.
- ⚠️⚠️ A FAILED RE-PROBE WAS ERASING A GOOD COUNT, EVERY TIME. The reading wrote {status:"unknown", n:null} over whatever was stored, so a state measured at 486,239 in the morning became "not measured yet" the moment a later probe timed out, and since the states that time out are exactly the large ones, the survey actively destroyed its own best numbers and drifted towards reporting the country as empty. Maharashtra answers in 22.4s and was sitting at unknown with no count for this reason and no other. A timeout is information about the register's gateway on one call, not about the state: it may reset the clock and nothing else. A clean zero still overwrites, because that is the register answering and a withdrawn roll has to be able to go back to empty.
- ⚠️ Shortening the probe ceiling is only safe BECAUSE of that carry-forward. Without it, a shorter timeout would delete the numbers of exactly the states worth counting.
- ⚠️ The per-state date is published as `checked_at`, not `measured_at`: a carried-forward reading resets the clock without re-measuring, and a fresh-looking date on an older number is the kind of small dishonesty that costs a reader's trust in the whole page.
- ⚠️ AND THE DISTRICT FLOOR WINS WHEREVER IT IS BIGGER, not only where there is no state figure at all. A state aggregated once and since walked district by district holds a larger, newer number in el_district_counts, and preferring the older one because it arrived first is the same mistake as the rest of this release.
- Gates: electoral-voters 456 (was 450), the carry-forward, the zero that does overwrite, and a state never measured still reading as unknown with nothing invented for it.
2026-08-27
e1.6.4, The page said 1.6 million and the index held at least sixteen
- ⚠️⚠️ "THE SIX STALEST STATES" IS WHAT THE COMMENT SAID AND IS NOT WHAT THE CODE DID. codes.filter(isStale).slice(0, 6) takes the first six in DECLARATION order, S01 to S29, then U01 to U08, and the head of that list goes stale again before anything below it is ever reached, so it took every refresh slot on every visit, forever. Measured live: S01 to S13 were 0.6 hours old, S14 to S21 twenty-six hours, and S22 to U08, Delhi, Bengal, Tamil Nadu, Rajasthan, Uttar Pradesh among them, forty-three hours, against a four-hour freshness rule. A rotation that is not ordered by age is not a rotation. It is now sorted by measured_at, oldest first, and the ordering has its own gate.
- ⚠️⚠️ SO SIX STATES WERE PUBLISHING "NO ROLL IN THE INDEX YET" OVER A LIVE ROLL. Measured the same day: Rajasthan 1,458,318 electors, Uttar Pradesh 445,324, Chandigarh 323,740, Jharkhand 41,637, Uttarakhand 8,448, and Chhattisgarh present in quantity. Only Haryana, the legacy J&K code, Lakshadweep, Puducherry and Ladakh return a genuine zero. A stale indexed reading is a number a little too small; a stale empty reading is a flat denial that the state is in the index at all. They are not the same mistake and no longer share a window, empty is re-asked every hour, a failure every hour, an indexed reading every two.
- ⚠️⚠️ A TEN-SECOND PROBE DOES NOT MERELY MISS THE BIG STATES, IT SELECTS FOR SMALL ONES. Measured state-scope response times: Rajasthan 12.7s, Andaman 13.3s, Arunachal 20.5s, Maharashtra 22.4s, Himachal 23.0s, Mizoram 37.6s, Nagaland 52.7s, Goa 54.0s, against Assam 4.6s and Kerala 2.0s. Every state the budget could measure was a state with little in it, so the survey was biased downwards by construction. The probe is 45 seconds now and no longer runs in front of the reader: whatever lands in a seven-second head start is in the response, the rest is finished in waitUntil and is there on the next load.
- ⚠️⚠️ A STATE TOO LARGE TO ADD UP IN ONE CALL IS NOT A STATE WITH NO NUMBER. geoByDistrictProbe has been measuring these district by district and writing them to el_district_counts since e1.4.1, and coverage never read the table: Delhi sat at 5,253,656 measured voters and West Bengal at 2,562,053 while the map called both "too large to measure quickly" and the headline counted them as zero. Those states now read as part-counted, carrying the floor their measured districts add up to and saying how many districts that is.
- ⚠️ AND THE HEADLINE IS THE VOTER COUNT NOW, WITH THE STATES MISSING FROM IT DIRECTLY UNDER IT. The card led with "10 states with roll data" and printed no total at all, so the only national figure a reader could form was by adding up a map that silently excluded every large state. A total that does not say what it leaves out is worse than no total.
- ⚠️⚠️ THE CONSTITUENCY HEADLINE WAS THE FILTERED COUNT AND HAD TO BE THE UNFILTERED ONE. totalPersons already collapses a voter's draft entry and their final entry into one person, that is exactly how it differs from totalOccurrences, so the roll-type filter was never what stopped double counting; it stops the FACETS double counting, because facets count entries. Publishing the filtered number as the headline instead dropped every voter who is on one list and not the other: Sadar Bazar is 196,894 people, 195,322 of them on the final roll, so 1,572 people, the ones on the newer draft, which is the list a reader most wants counted, were missing from the headline of their own constituency. The count is unfiltered and each person is in it once; the splits stay inside one roll type and the page says so when the two denominators differ.
- ⚠️ AND THE ROLL TYPE IS CHOSEN BY RECENCY, NOT BY VOLUME. A revision publishes a draft first and a final after it, so while a NEW revision is being published the OLD final still carries more entries than the new draft, and "sort by count" then reads the superseded list and calls it current. Prefer a final; every final type present, so no SIR state is silently dropped; an unrecognised label ranks above a draft because it is not evidence of one; and a draft-only area is still measured on its draft. This is currentOccurrences()'s rule from e1.6.2, one product late.
- ⚠️⚠️ EVERY COUNT IN A STORED REPORT IS A MEASUREMENT OF A MOVING INDEX, AND THE PAGE DID NOT SAY WHEN IT WAS TAKEN. Measured over three hours on two published reports, same queries re-run: BRIJESH KUMAR nationally 18,654 to 19,459, ANISHA 531,136 to 552,108, about 1.3% an hour while the roll is being loaded. Reports now date their own counts and say which way the error runs, and because the line is built at render time from generated_at, every report already in D1 gains it with no re-run. It says the counts are LOW, never that they are wrong: the index only grows, so a stale namesake count understates and the identification it supported was made against a narrower field than exists today.
- Cache windows cut while the load runs: the voters payload from four hours to one, the coverage payload to fifteen minutes now that it is served out of D1 with the register work behind it. Put them back when the ingest is complete and the numbers stop moving.
- Gates: electoral-voters 450 (was 423), the rotation order, the per-status windows, the part-counted merge, the total travelling with what it omits, the roll-type preference and the report's own staleness line. electoral-calibration 178, electoral-page, court calibration 658 unmoved.
2026-08-27
e1.6.3, A voter ID is a filing number, and the tree's working is read by people
- ⚠️ THE LOUDEST THING ON THE IDENTITY CARD WAS THE ONE FIELD NOBODY RECOGNISES A PERSON BY. The EPIC sat under the name as a highlighted chip, above the relative, the age and the roll. On a report whose entire subject is "is this the right person", a prominent reference number reads as proof of one. It is a field like the others now, in the list, last, because that is where a filing number belongs.
- ⚠️ "HOW THE LINK WAS ESTABLISHED" WAS WRITTEN IN THE ENGINE'S VOCABULARY. "Linked across neighbouring households in the same roll part" and "their own roll entry names a relative who is in this household" are both exactly true, and both ask the reader to already hold a model of roll parts, household keys and relative-naming before the sentence means anything. Every row is saying one of three simple things, whose entry named whom, who lives where, and whether the register said it or we worked it out, and all three can be said in the words people use about their own families.
- ⚠️ WHAT SURVIVES THE PLAINER WORDING IS THE HEDGE. A link the register states and a link inferred from who lives at an address are different claims, and this column exists precisely because the diagram above it cannot tell them apart. "Named on their entry" and "worked out from" stay visibly different sentences.
- ⚠️ AND "NO ENTRY OF THEIR OWN" IS NOT "NOT ON THE ROLL". Somebody a relative named, whom these searches did not find, may be enrolled in another district entirely. The line says what was looked for, not what exists, and the tree legend now says the same thing.
- The column is headed "How the link was found", which covers both the links the register states and the ones worked out, where "established" quietly claimed the stronger of the two for all of them.
- Gated: every branch of basisOf is asserted to be a sentence, under fourteen words, free of roll-part and roll-entry vocabulary, with the singular case reading as a singular and the inferred case reading as an inference.
2026-08-27
e1.6.2, The draft roll is not a second person, and an address is not one line
- ⚠️⚠️ THE VOTERS PAGE LEARNED AT e1.4.6 THAT AN ELECTOR IS PUBLISHED TWICE IN A REVISION, ONCE ON THE DRAFT ROLL AND ONCE ON THE FINAL, AND THE INDIVIDUAL REPORT HAD NEVER BEEN TOLD. Sadar Bazar is 196,894 people and 388,762 roll entries for that reason. Read one person at a time the same doubling arrives as an occurrences array of length two, and it was published three ways: as "appears 2 times across roll revisions", as two rows on the address timeline with an invented "address changed" between them, and, when the draft happened to sort first, as the DRAFT roll's part number, serial and polling station on the identity card.
- ⚠️ THE PART, SERIAL AND POLLING STATION ARE THE NUMBERS A READER CARRIES TO A BLO, AND THEY WERE DECIDED BY ARRAY ORDER. pickGeoOcc ranked by PIN, district, state, deletion and year; a draft and its final roll agree on every one of those, so the tie fell to whichever the register happened to return first. The roll type is now the tiebreak, and it sits BELOW the year deliberately, this year's draft is more current than a two-year-old final, and a preference that outranked the year would publish a stale address to avoid a provisional one.
- ⚠️ PREFER, NEVER REQUIRE. Some areas are published as a draft and nothing else; hardcoding a final roll would report them as having no voters, which is e1.4.6's own warning and nothing here undoes it. currentOccurrences() drops a draft only where its own final roll has replaced it, in the same year AND the same constituency, so a draft in one state and a final in another the same year stays two entries, that is a move, and it is a fact about the person.
- ⚠️ AND "FINAL" IS NOT ONE STRING. The Special Intensive Revision publishes as SIR-FinalRoll and states moved to it at different times: measured 2026-08-27, the name ARMAN SHAIKH is SIR-FinalRoll 296, FinalRoll 170, DraftRoll 2. A test against the literal "FinalRoll" silently demotes every SIR state. Anything matching /final/i is a final roll, and an unrecognised type ranks BETWEEN final and draft, because a label nobody has met is not evidence of a draft.
- The namesake map is drawn from a geoFacetPath facet, and a facet counts ENTRIES. It is a comparison BETWEEN states, so the doubling does not cancel: a state publishing both rolls looked twice as much the home of a name as a state publishing only its final. One rollType facet call now measures which types the name is published on, and where a draft sits alongside a final the map is remeasured filtered to every final type present.
- ⚠️ EVERY FINAL TYPE PRESENT, NOT THE DOMINANT ONE. The voters page picks the single commonest type because it measures one area, where there is one answer. Filtering a national name to the dominant type would have deleted every FinalRoll state from the map. Where the filter cannot be applied the caption says the counts are entries and that a person on two rolls is counted twice; where it can, it says each elector is counted once.
- ⚠️ THAT CALL PAYS FOR ITSELF TWICE. It carries totalPersons for the same query, which is a second national count, the one number the whole prior rests on, and the one whose silent failure made "Rajesh Kumar" score as a rare name.
- The identity card drops the "Appears, N time on the indexed roll" row and the footnote pointing at the ECI section, both of which said something the reader had not asked and one of which was counting publications rather than revisions.
- ⚠️ AND THE ADDRESS IS AN ADDRESS NOW. It printed the register's fullAddress as one run-on line and then repeated three of its parts underneath: live on EC-TGQPZHZ that read "Tandulwadi, Ada ale Basti Tadulavadi, tandulavadi, baramati, baramati ramin, Pune, Maharashtra, 413102", followed by House Tandulwadi, District Pune, Maharashtra and PIN 413102, the village written three ways because every field is transliterated from its own Indic original, and the last three fields written twice. It is four postal lines now and the definition list is gone.
- ⚠️ THE DE-DUPLICATION IS PHONETIC, NOT LITERAL, Tandulwadi and tandulavadi are not equal strings and never will be. It is the same fold that lets chaitanya das and taitanya das be one father, used where being wrong costs a line of an address rather than a family. ⚠️ And a segment is dropped only when EVERY token in it has already been said: baramati and baramati ramin are the town and the rural block, and a substring test would delete a level of the address.
- ⚠️ TITLE-CASING EVERYTHING DAMAGES THE FIELDS THAT WERE ALREADY RIGHT. 12-B becomes 12-b, H.NO 44/2 becomes H.no 44/2, NCT of Delhi becomes Nct Of Delhi. A segment already carrying mixed case is the register's own spelling and is left exactly as it stands; only the transliterator's all-lower and all-upper output is touched, and never a segment with a digit in it.
- ⚠️ "EVERYONE REGISTERED AT THIS ADDRESS" AND "LIVING AT THE SAME ADDRESS, RELATIONSHIP NOT ESTABLISHED" WERE THE SAME PEOPLE TWICE. coResidents() builds its list as the household MINUS whoever the family tree placed, so every name in the second section had already been printed a screen above, with nothing on either heading to say which to read. One section now. What the second heading said is a property of a PERSON rather than of a list, so it is a column: the kinship the tree established against the rows it holds, "Not established" against the rest, and the same column on the houses either side, where in a compound the neighbours are usually the family.
- Gates: electoral-calibration 178 (was 164), electoral-voters 423, electoral-page all sections in place, court calibration 639 unmoved.
2026-08-26
e1.6.1, A community with no religion is still a community
- ⚠️⚠️ MEASURED LIVE ON UDALGURI, A BODOLAND DISTRICT, MINUTES AFTER e1.6.0 SHIPPED: 91 electors read as Bodo, while the headline said 63 placed in a community and the chart swept the Bodo rows into the grey "not recognised" wedge. The count was taken off the RELIGION layer alone.
- ⚠️ AND THE ROWS IT PUNISHED ARE THE ONES THAT ARE MOST CAREFULLY RIGHT. Bodo, Mising and Nyishi clans, the Mahar cluster, the Oraon clans, Gill, Sethi, Khatri and a Jammu and Kashmir Gujjar all carry a deliberate religion of unknown, because the same clan is Hindu and Christian, or Hindu and Sikh, and claiming one is the worst error this table makes. The design decision was correct and the accounting then treated it as a failure.
- `identified` keeps its narrow meaning, because it is the denominator the religion percentages are drawn from. `placed` is the new headline: read into any layer at all. The unrecognised remainder is measured against `placed`, so it now holds only what was genuinely not recognised.
- ⚠️ AND THOSE COMMUNITIES GET A NAMED SLICE ON THE CHART. Drawing them as part of the grey wedge made the largest real community in a Bodoland or Jharkhand booth the least visible thing on the page.
- Gates: electoral-voters 423 assertions.
2026-08-26
e1.6.0, The surname systems the table could not see at all
- 89 net-new community readings and 16 more shared names, all state-scoped. The earlier waves had already covered the north Indian, Maratha, Jain and schedule batches; what was left were whole naming SYSTEMS with no row anywhere, not individual gaps.
- ⚠️ THE SINDHI DIASPORA HAD NO ROW AT ALL - a large urban trading community across Mumbai, Gujarat, Delhi, Rajasthan and MP, invisible to the analysis. Advani, Vaswani, Mirchandani and eleven more now read as Sindhi Hindu. No legal category is inferred from them: Sindhi is a community and a language, not a rung, so the caste layer stays blank and the community layer does the work.
- ⚠️ THE NORTHEAST CLAN SYSTEMS: Bodo (Basumatary, Daimary, Narzary, Musahary), Mising (Pegu, Doley, Payeng), Pnar (Suchiang, Lamare), Sumi Naga (Yepthomi, Zhimomi), Nyishi (Nabam, Taba, Techi). Religion is unset on every one of them, because these clans are Christian, Hindu and traditional-religion alike and the clan name says nothing about which.
- ⚠️ DENOTIFIED AND NOMADIC IS ITS OWN CATEGORY, NOT A SYNONYM FOR OBC. Ramoshi, Kaikadi, Kanjarbhat, Kolhati, Pardhi, Vaghri, Chhara, Gadia Lohar and the Tamil Nadu Koravar are read as denotified or nomadic rather than filed under backward class, because collapsing them erases a distinction those communities themselves fought for. New caste-group value `dnt`.
- Also new: the Garhwal and Kumaon Brahmin surnames (Nautiyal, Semwal, Bahuguna, Uniyal, Thapliyal, Kandpal) as state-high rows and never All India; Punjabi Christians (Masih, Maseeh, Massey) reading RELIGION and nothing else, because Masih is taken precisely for carrying no former caste; Saitwal Jain; exact Ramgarhia; the Kerala, Karnataka, Andhra and coastal fishing communities; the Rajasthan and Gujarat pastoral rows; two Jewish surnames, and a `jewish` religion to hold them.
- ⚠️ TOKENS THAT LOOK LIKE CLAN NAMES AND ARE NOT are the other half of the wave, and they are dangerous precisely because each sits beside a real system: Brahma is a Bodo surname AND an Assamese title AND a given name; Tandel is the skipper of a boat, carried by every coastal community; Dhar is Kashmiri Muslim, Kashmiri Pandit and Bengali at once; the Punjabi clan names Birdi, Virdi, Bhambra, Rehal, Riat and Mankoo cross Ramgarhia, Jat and Khatri families. All sixteen are shown as themselves.
- ⚠️ THREE SURNAMES LANDED IN NEITHER TABLE. Brahma, Dhar and Rehal were removed from the community table on the way to the shared-name list and the insert skipped them as already present, so they stopped resolving entirely. A token in both tables is a contradiction and a token in neither is a silent hole; the suite now fails on either.
- ⚠️ A TYPO IN A STATE CODE MAKES A ROW SILENTLY UNREACHABLE - it looks right on the page, never matches a roll, and nothing else notices. Every reading is now checked for shape, for a real religion and caste key, and for real ECI state codes.
- ⚠️ A FIXED TIMESTAMP IN A FIXTURE IS A TEST WITH A FUSE, and this one went off. Two cache stubs stamped a literal date into the row they wrote; the caches expire after four hours, so once the clock passed it the write looked stale on arrival, the survey never converged, and four assertions went red on the day after they were written with no code change. Same class as the fixed future hearing date in the court suite.
- Gates: electoral-voters 415 assertions (up from 365), electoral-calibration 164, every page renders, calibration 624. The map now holds 1,400 surnames, 1,407 readings and 545 communities.
2026-08-25
e1.5.0, One spelling, two peoples: the state is part of the key
- ⚠️⚠️ BHAT IN JAMMU AND KASHMIR IS A MUSLIM KASHMIRI KRAM. BHAT IN COASTAL KARNATAKA, GOA AND MAHARASHTRA IS A KONKANI SARASWAT BRAHMIN. The table held one row per surname, so it had to pick one, and whichever it picked was flatly wrong for the other half of the country. A surname now carries one row per reading with disjoint state lists, and the state on the roll chooses between them.
- ⚠️ OUTSIDE EVERY LISTED REGION A SPLIT SURNAME RESOLVES TO NOTHING, AND THAT IS THE POINT. The one-step confidence drop that correctly serves a migrant Patel in Delhi is exactly wrong here: there is no slightly-less-certain answer between Muslim and Hindu Brahmin. A Bhat on a Delhi roll is shown as the name itself, in the same slice as Singh, which is the truth about what the record supports.
- ⚠️ THAT BUCKET IS REACHED THROUGH THE LOOKUP, NOT BY READING THE SHARED-NAME CONSTANT. A split surname lives in the surname map rather than the shared-name list, so the direct read sent a Delhi Bhat to "not recognised" - the exact bucket this layer exists to empty. Only the state can tell, so only the lookup can answer.
- ⚠️ THE LEGAL CATEGORY IS STATE LAW, NOT A PROPERTY OF THE NAME. Gujjar is a Scheduled Tribe in Jammu and Kashmir and a backward class in Rajasthan; Rajbanshi is a Scheduled Caste in West Bengal and Tripura while the same people in Assam are Koch Rajbongshi and OBC; Jat is a dominant landholding caste in Haryana and Delhi and OBC in Rajasthan. Seven surnames are split this way so far.
- ⚠️ FOUR RELIGION-LOCKING ERRORS CORRECTED. Sethi was published as a Hindu Vaishya name; it is a Khatri or Arora one and the religion stays unset, because the same clans are Hindu and Sikh. Shaikh and Sheikh no longer publish a caste group at all - the name is claimed across the whole social range. Memon, Khoja and Bohra are Gujarati trading communities, not Pasmanda. The Oraon clan names carry no religion, because the same clans are Hindu and Christian.
- ⚠️ WHERE TWO READINGS AGREE ABOVE THE COMMUNITY, THE COMMUNITY IS WHAT THE SENTENCE NAMES. Pillai is a landholding caste in both Kerala and Tamil Nadu, a different one each time, and "a landholding caste in one part of the country and a landholding caste in another" explains nothing. It now reads Nair and Vellalar.
- ⚠️ THE GIVEN-NAME TABLE IS PUBLISHED. The religion layer leans on first names harder than on any surname - a Mohammad Sharma is Muslim - and a signal nobody can inspect is a signal nobody can correct. It has its own filterable section on the community map, beside the surnames and the shared names.
- Also corrected: Mohanty is a Karan writer name in Odisha rather than a Brahmin one; Sahu is a community reading in the east rather than a shared name; the Konkan coast Catholic surnames are Konkani Catholic rather than Goan everywhere; Acharya and Goswami are attested regionally rather than All India; Dutt is split between the Bengali Kayastha and the Punjabi Khatri readings.
- Gates: electoral-voters 365 assertions (up from 279), electoral-calibration 164, every page renders, calibration 624.
2026-08-25
e1.4.7, A partly loaded area is not a small area
- ⚠️⚠️ CHANDNI CHOWK PUBLISHED 18,389 VOTERS, WHICH LOOKS PLAUSIBLE AND IS NOT. A Delhi assembly constituency holds well over a hundred thousand. Polling parts are numbered contiguously from 1, so probing the first sixty settles it: Sadar Bazar has 60 of 60 present and 75,589 voters in them; Chandni Chowk has 10 of 60 and 7,559. Fifty of its first sixty booths are simply not in the index yet. The count was true about the index and false about the constituency, and nothing on the page said which.
- ⚠️ THE TEST IS DENSITY, NOT SIZE. A genuinely small constituency has a short CONTIGUOUS run of parts; a half-loaded one has holes in the middle of the sequence. Twelve cheap probes at the start of the range tell them apart in a couple of seconds, and below two thirds present the area is reported as partly loaded.
- When it fires, the warning comes first and the headline changes its words: "18,389 voters loaded so far, not the whole constituency" rather than "18,389 voters on the roll". Above constituency level there are no part numbers to probe, so no claim is made either way rather than guessing.
- ⚠️ Note for anyone reading a Chandni Chowk figure: the ASSEMBLY constituency holds a little over a hundred thousand voters. The million-plus figure belongs to the Chandni Chowk PARLIAMENTARY constituency, which spans ten assembly seats and is not a level this page measures.
2026-08-25
e1.4.6, Every figure counts voters
- ⚠️⚠️ IT WAS NEVER TWO LANGUAGES. A voter is published TWICE in the same year: once on the draft roll and once on the final roll. Measured on Sadar Bazar: 196,894 people against 388,762 entries, and the rollType facet splits it exactly, FinalRoll 195,470 plus DraftRoll 193,292. The langCode facet is a single value, ENG, for all 388,762. Because the register's facets count entries, the sex split read 206,947 men in a constituency with 196,894 voters.
- ⚠️ MEASURING INSIDE ONE ROLL TYPE MAKES EVERY FACET A COUNT OF PEOPLE. `rollTypes=FinalRoll` on the same constituency returns 195,322 people and 195,470 entries, a ratio of 1.0008, and the sex split then sums to the electorate. Every split on the page is measured that way now, and the words entries, occurrences and revisions are gone from the reader's view entirely.
- ⚠️ `rollTypes` IS PLURAL. `rollType=FinalRoll` is accepted, silently ignored, and hands back the doubled numbers with no error. Same class as `litigants=` against `litigant=` on the court API: a wrong parameter name that fails by looking like it worked.
- ⚠️ AND THE ROLL TYPE IS MEASURED, NOT ASSUMED. Some areas are published as a draft and nothing else, so hardcoding FinalRoll would report them as having no voters. One cheap facet call finds the dominant type for the area and carries the totals at the same time, and a draft-only area is measured on its draft.
- The reader is only ever offered people. `entries_all_rolls` stays in the payload for provenance and appears nowhere on the page.
2026-08-25
e1.4.5, Entries are not people, and a missing surname is not a missing entry in our table
- ⚠️⚠️ THE DROPDOWN SAID 3.88L AND THE CARD BELOW IT SAID 1.9L, AND BOTH WERE RIGHT. The constituency list was labelled with the geoFacetPath figure, which counts ENTRIES, while the report counts PEOPLE. On a Delhi roll those differ by a factor of two: Sadar Bazar is 388,762 entries and 196,894 voters. The pair was nonsense even though neither number was wrong. Every dropdown now carries the place and nothing else: the list is for choosing where to look, not for quoting a figure, and the one place a raw entry count still appears is the map tooltip, which says the word "entries" out loud.
- ⚠️⚠️ "NOT RECOGNISED" WAS OVER HALF OF SADAR BAZAR, AND ALMOST NONE OF IT WAS A GAP IN OUR TABLE. Measured on 1,200 rows: the unrecognised half was Rani, Pooja, Anita, Nisha, Rahul, Deepak, Sanjay, Neha. Those are not surnames we are missing. They are GIVEN NAMES, because the roll records no family name at all for that voter and the last word of the name is a second given name. Blaming our table for what the record never contained put half a constituency in a bucket that told the reader nothing. Split out: 48% mapped, 33% no family name on the roll, 20% genuinely unknown. Live, the constituency now reads 11% placed in a community, 43% a widely shared name, 29% no family name on the roll, and 17% not recognised.
- ⚠️ THE TEST IS THE POPULATION'S OWN FIRST NAMES, NOT A LIST OF GIVEN NAMES. A token that appears as a FIRST name across this same roll is a given name here, whatever it means elsewhere. That makes it self-calibrating: it is right in Tamil Nadu, where the last token is usually the father's given name, for exactly the same reason it is right in Delhi, and it needs no per-state table. Two sightings, or one in a thousand rows, whichever is larger. ⚠️ The surname map still wins: Sharma is a surname whether or not somebody in the booth is called Sharma first.
- Rani, Kumari, Wati, Bala and Bai join Devi as female suffixes shown as themselves. Rani alone is the commonest last token in Old Delhi.
- Estimated counts now read "1.7% (~3,347)" rather than "1.7% about 3,347".
2026-08-25
e1.4.4, Notable first, parents that open a door, and figures that line up
- The notable card now sits above the polling station and booth lists. It is the part people read; the lists below it are navigation.
- ⚠️ A PARENT IS OFTEN NOT ON THIS ROLL THEMSELVES, so their row now opens the YOUNGEST of their children at the house most of them share. Both halves matter: the house because that household is the family, and the youngest because they are the most likely to still be at that address. The row shows the house number and says who the link opens, so nobody clicks blind. Households do the same.
- ⚠️ THE FIGURES DID NOT LINE UP, AND THE REASON WAS ONE WORD OF CSS. The label column in every ranked list was sized `auto`, so it grew to the longest name and pushed the bar and the number right by a different amount on every row: a column of percentages read as ragged and wrong even though every number was correct. The label is now a fixed fraction, the bar takes the slack, and the figure column is fixed width, right-aligned and set in tabular figures so digits sit under digits. Numeric table cells and their headers are right-aligned together, and the voter list's serial, age and booth columns are numeric.
- ⚠️ THE BACKTICK TRAP, FOR THE FOURTH TIME. A CSS comment inside a template literal contained backticks and ended the whole stylesheet string. `node --check` is the only thing that catches it, so the test suite now imports the stylesheet and asserts it is whole and still defines its key classes, rather than relying on somebody remembering to run the check.
2026-08-25
e1.4.3, Numbers that match the area, and copy written for a reader
- ⚠️ A SAMPLE COUNT PRINTED BESIDE AN AREA TOTAL READS AS A BROKEN NUMBER. Model Town in North Delhi has 14,238 voters and the names card showed "Kumar 51", because the surname analysis read 600 rows. It now reads 3,000, which is a fifth of a large constituency and the whole of a small one, and every figure is shown as a SHARE with an estimate for the whole area beside it. The card says which it got: "read from all 2,371 voters here" or "read from 3,000 voters taken from across the 14,238 here".
- This is affordable because the register's gateway is longer now: fifteen pages of a hundred rows came back in 4.9 seconds at three lanes.
- ⚠️ RAISING THE GATEWAY DID NOT MAKE STATE-LEVEL AGGREGATES POSSIBLE, IT MADE THE FAILURE SLOWER. A state-scope facet on Delhi used to fail at 20s; it now runs to about 60 seconds and returns HTTP 499 with an empty body. Our own 25s ceiling is what keeps the page responsive, and the district-by-district route is still the way a large state gets measured. Worth knowing before anyone raises it again.
- Religion, caste and community are now three full-width rows stacked down the card rather than three cramped columns, each with the plain point a reader would take away beside it: "Hindu majority", "Hindu and Jain together", with the top two named and their shares, and how much could not be placed.
- ⚠️ EVERY NOTABLE ROW OPENS A REPORT NOW. A house number on its own is a dead end; the youngest voter in it is a door into the household. Households, oldest voters and parents all carry somebody's voter ID and open their own report. Youngest rather than first, because they are the most likely to still live there.
- ⚠️ THE HEAVY CARDS ONLY RUN ON SOMETHING WORTH READING. Nothing is measured above the district, and above a million voters the names and community cards are replaced by a line explaining that a three-thousand row sample is a fraction of a percent of an area that size and pointing at the constituency picker. A chart nobody should trust is worse than no chart.
- Copy rewritten throughout for the person reading the page rather than the person who built it. "There is no booth facet on the register, so booths are found by sampling" is now "Every booth here and how many voters it has". "That is a measurement failure, not an empty roll" is now "we could not measure it, not that nobody lives here". Roll entries are "entries on the roll", electors are voters, and the technical explanations that only made sense to the engine are gone.
2026-08-25
e1.4.2, Shared names shown as themselves, and a surname table four times the size
- ⚠️⚠️ "UNIDENTIFIED" WAS LYING, AND THAT IS THE HEADLINE FIX. Singh, Kumar, Devi, Das and their kind have no community row, so every one of them fell into "not identified". In North West Delhi that is most of the roll: a booth that is mostly Singh and Kumar was published as almost entirely unidentified, which tells a reader nothing true. There are THREE honest outcomes, not two: a community reading, a **recognised shared name shown as itself**, and the genuinely unrecognised remainder. The chart now carries one slice per shared name, labelled "Singh (multiple possibilities)", coloured by kind (Hindi-belt filler, title, eastern polyvalent, Assam title, collision, Christian given name) and never merged into a single lump, because the whole point is that the reader sees WHICH names they are.
- ⚠️ THE REASON STAYS PUBLIC. Absence from the table was never the argument; the reason is. Every shared name carries its own sentence on the community map page: Singh is Sikh AND Rajput AND eastern-UP Hindu AND Gorkha. That is what stops somebody "completing" the table by mapping it. The page section is now "Names shown as themselves" rather than "Names left out on purpose".
- ⚠️ A SHARED NAME ON THE RELATIVE DOES NOT COUNT. A woman whose father is a Singh is not herself in the Singh slice; only the elector's own surname decides.
- **The surname table went from 318 entries to 1,316, and from 171 communities to 507**, built from the SC/ST/OBC schedules and the state-by-state expansion research. Coverage now includes the Scheduled Caste and Scheduled Tribe schedules state by state, Meitei yumnak, Naga and Kuki clans, Gorkha surnames, Santhal and Munda and Ho and Oraon and Gond clans, Khatri and Arora clans, Maratha and Mahar clusters, Tuluva and GSB names, and the Kashmiri kram.
- ⚠️ FOUR CORRECTIONS THE RESEARCH INSISTED ON, ALL OF WHICH THE OLD TABLE GOT WRONG. Jat clan surnames (Gill, Sidhu, Sandhu, Dhillon and the rest) had religion locked to Sikh, which mislabels every Hindu Jat in Haryana, Rajasthan and west UP: religion is now unset and the given name decides. Khatri and Arora clans the same. The Mahar cluster the same, because many Mahars are recorded Hindu and many Buddhist and forcing either is wrong. Oraon clans the same, because the same clans are Hindu and Christian and splitting them by surname was the wrong axis.
- ⚠️ THE REPORT SAYS IT IS TENTATIVE, IN THE PAYLOAD AND NOT ONLY ON THE PAGE. Four claims travel with every set of numbers: this is a tentative estimate and not a census, it describes an area and never a named elector, surnames are read only in the states they are attested in, and shared names are shown as themselves rather than guessed at. It is in `MIX_BASIS` inside the JSON so an importer or a model cannot take the percentages without it.
- ⚠️ THE MAP NOW SHOWS ONE LEVEL UP WITH THE SELECTION LIT. A district drawn on its own is a shape nobody recognises; the same district picked out inside its state is instantly readable and also shows what is next door. Choose a district and the state is drawn with that district's constituencies lit; choose a constituency and the district is drawn with that constituency lit. It needs no state-wide constituency-to-district map: the constituencies of the selected district are already known, so highlighting is a set membership test.
- ⚠️ NO CACHE ON MEASUREMENTS. The roll is ingested continuously, so any cached number is a number that can be wrong, and correctness beats the saving here: the queries are few and the register is fast for everything except a whole state. What is still remembered in D1 is structural discovery that costs dozens of probes and measures nobody, namely which states hold a roll and which district codes exist.
- The selection band is thinner and auto-hides on the way down, returning on the way up: the cards run to several screens, so a band that is always there costs a strip of every one of them and a band that never returns means scrolling to the top to change booth.
2026-08-25
e1.4.1, The constituency dropdown, and a cache that stops lying
- ⚠️⚠️ THE CONSTITUENCY DROPDOWN WAS EMPTY FOR EVERY LARGE STATE, AND IT LOOKED LIKE A DEAD CONTROL RATHER THAN A FAILURE. `part=geo` probed at STATE scope, which is exactly the call that times out on Bengal, Delhi, Tamil Nadu and the rest; when it fell back to enumerating district codes it returned `acs: []`, so a reader who opened /voters?state=S25&district=S2519 found the constituency select disabled and empty with nothing on the page explaining it. A DISTRICT-scope geoFacetPath answers the same question and is not slow: measured on Bankura, **0.58 seconds**, returning 2/S25/S2519/258 = 2,074, /249 = 884, /248 = 828. Split out as `part=acs`, fetched when a district is chosen, cached on its own key so switching districts no longer re-runs the state enumeration.
- ⚠️ THE CACHE ASSUMED GEOGRAPHY DOES NOT MOVE. That is true of boundaries and false of this index: the roll is being ingested continuously, so a district that held nothing yesterday holds two hundred thousand electors today, and a six-hour cache would keep insisting it is empty. Measurements are cached for **one hour** now, the coverage survey's D1 rows for **one day** rather than a week, and boundary geometry still for a week because boundaries genuinely do not move.
- ⚠️ AND STALENESS IS VISIBLE RATHER THAN SILENT. Every payload carries `measured_at`, the selection band prints how old the number on screen is, and a **Refresh** control re-measures the current view with `refresh=1`, which skips the cache read but still writes the answer back under the ordinary key so the next visitor gets the fresh number rather than the stale one it just replaced. The forced pass lasts one round and then the brake is back on.
- ⚠️ THE SELECTION IS A STICKY BAND ON DESKTOP. State, district, constituency, polling station and booth stay visible with the breadcrumb while the cards scroll. The cards run to several screens, so without it a reader scrolling the booth analysis had no idea which booth it was, nor any way to change it without scrolling back. On a phone it is a normal block, because a sticky bar five controls tall would eat the screen.
- ⚠️ THE BACKTICK TRAP, HIT FOR THE THIRD TIME. A CSS comment inside the page template literal contained backticks and ended the whole page string. `node --check` catches it and nothing else does. The comment now says so, next to itself.
- ⚠️ NOTE ON THE 20 SECOND LIMIT: it is the register's OWN gateway, not a timeout in this code. A state-scope facet on Delhi returns HTTP 500 with the register's `service_unavailable` body and its own `reference_id` after 20 to 23 seconds, while the client is willing to wait 25. Raising it is a change on the register side, and it would make state-level aggregates possible for the seventeen states currently shown as unmeasured.
2026-08-25
e1.4.0, The community map, polling stations, and why Haryana was blank
- ⚠️ NEW: /voters/communitymap publishes the surname table in full. Every community percentage on the explorer rests on a table of readings about what surnames mean, and a table like that kept private is an assertion nobody can check. Three layers: religion, caste group, jati. Around 300 surnames seeded, naming about 90 distinct communities, every entry carrying a confidence and the states it is attested in. It is deliberately incomplete, and improving it is the single highest-leverage change anyone can make to the analysis because nothing else in the pipeline has to move for the output to get better.
- ⚠️ A SURNAME MEANS DIFFERENT THINGS IN DIFFERENT PLACES, so region is evidence rather than a filter. A Patel read in Gujarat keeps high confidence; the same Patel read in Bihar keeps the reading and drops a step, because families move. A low-confidence surname read outside its region is dropped entirely.
- ⚠️ CASTE IS NEVER READ WITHOUT RELIGION AGREEING. Without that gate a row whose given name is Mohammad and whose surname is Sharma publishes as a Muslim Brahmin. And a given name outweighs a surname on religion: weighted equally the two cancelled and the row went to unidentified, losing the highest-recall signal on this register.
- ⚠️ NAMES REFUSED AN ENTRY ARE AS IMPORTANT AS NAMES GIVEN ONE, and each is pinned by test: Singh (Sikh, Rajput, Bihari, Gorkha), Kumar, Devi, Das, Shah, Malik, Rao, Naik, Pradhan, Chaudhary. Kaur is included because it is genuinely decisive. Measured on Pune AC 201 the map identifies 127 of 600 legible names and reads maratha 68, deshastha 20, brahmin 32: useful, and honest about the 79% it cannot name.
- ⚠️⚠️ HARYANA WAS BLANK BECAUSE ITS ROLL IS NOT IN THE INDEX, AND THE PAGE HAD NO WAY TO SAY SO. Probed all 38 states: fourteen return a clean ZERO, among them Bihar, Uttar Pradesh, Madhya Pradesh, Rajasthan, Jharkhand, Chhattisgarh, Uttarakhand, Himachal, Chandigarh, Puducherry and Ladakh. `q=Haryana` with no state filter returns 941 rows, every one of them in Bengal, Delhi, Andhra or Telangana where the word appears in an address. The India map is now shaded by coverage and a state with no roll says so on hover. ⚠️ AN ERROR IS NOT A ZERO: Delhi, Bengal, Tamil Nadu, Karnataka, Andhra and Telangana all fail the probe and all certainly have rolls, so they are marked unmeasured, never empty. Collapsing the two would draw Delhi as a state with no voters.
- ⚠️ A LARGE STATE IS NOW ENUMERATED DISTRICT BY DISTRICT, AND NO FACET IS CHEAPER. Measured on Delhi: geoFacetPath, districtCode AND acNumber facets all fail at the register's own 20s gateway limit, so waiting longer cannot help because it is their gateway giving up rather than our timeout. But a district code is the state code plus two digits and a plain count on ONE district answers in about 15s. U0502 Old Delhi 247,826, U0503 North 304,145, U0512 South 752,357. The probe stops after a run of consecutive misses rather than walking all 45.
- ⚠️ NEW LEVEL: POLLING STATION, between constituency and booth. A station is one building and usually holds several parts; the station name carries its own part number as a prefix, so parts 57, 58 and 60 in Delhi all read '<n> - sfctor-11. rohini, ...' and stripping the number is what turns three booths into one building.
- ⚠️ AND THE FIRST CUT OF THAT GROUPING PRODUCED AN ABSURDITY. On Pune AC 201 the transliterator rendered many different buildings as the single word 'starling', so grouping on the string alone merged FIFTY unrelated booths and 52,095 electors into one polling station. A station name shorter than eight characters is not a name, and a station holding more than twelve parts is not a building: both fall back to ungrouped booths rather than a fiction.
- ⚠️ EVERY CARD FETCHES ITSELF WHEN IT SCROLLS INTO VIEW, with a live timer. Measured on Pune AC 201 the summary answers in 9.6s, the name analysis in 51s and the station sweep in 24s; loading them together meant the reader watched a shimmer for a minute while the numbers they asked for sat ready. Nothing was cheapened to make this faster: the sampling depth is unchanged and the slow cards simply say how long they have been working.
- ⚠️ THE URL IS NOW THE STATE. State, district, constituency, station, booth and page all live in the query string, so a view is shareable, bookmarkable and edge-cacheable, and back and forward work. Coverage is cached for 24 hours and everything else for 6.
- ⚠️ NO EM DASHES, AND THE REASON THEY SURVIVED SO LONG. `withoutLongDashes` rewrites them in the page shell, but text delivered as JSON from the API and inserted into the DOM by script never passes through it, which is exactly where they were showing up. A test now fails the build on any en or em dash in an API payload or a rendered page.
- Design: one card per analysis stacked down the page, a shared card system so the explorer and the community map cannot drift apart, male and female figure icons at one per fifty electors instead of a bare donut, and a fixed type scale.
- ⚠️ Unchanged and non-negotiable: no elector anywhere carries a religion, caste or community label. The classifier that produces a per-row verdict is not exported, the elector payload enumerates its keys rather than spreading the register's row, and the test suite fails the build if a religion, caste, jati or community key ever appears on a serialized elector.
2026-08-25
e1.3.0, /voters, the roll read as a place, not as a name
- ⚠️⚠️ THE REGISTER CAN BE BROWSED GEOGRAPHICALLY AFTER ALL, AND NOBODY HAD NOTICED. A filters-only search is a clean 400: "Supply at least one of name, epic, q, or a complete household key", which is why every electoral page so far has demanded a name. But `q` is matched against the row's OWN geographic text, so any word that is true of the scope matches every row in it. Measured on Odisha AC 36 part 6: `q=Bhograi` (the constituency), `q=Balasore` (the district) and `q=Odisha` (the state) each return **850**, `q=zzzzqqq` returns 0, and the same q against part 7 returns 0, so q is genuinely applied and so are the filters. It is the true total, not a slice: `nameMatchMode=any` over fourteen of the commonest local name tokens reaches 466 of those 850 and never exceeds it, and the district's own geoFacetPath independently reports 850 against that constituency. `q=<state name>` plus geographic filters is therefore a complete enumeration primitive, and the whole page is built on it.
- ⚠️ THERE IS NO partNumber FACET AND geoFacetPath STOPS AT LEVEL 2. State, district and constituency totals are one cheap call each; booths are not countable that way at all. They are found by sampling rows for distinct part numbers and then counted one exact call each, and sampling ALONE is far too thin on a large seat: six pages of a 371,039-elector Delhi constituency found NINE booths covering under 4% of it, because the register returns rows in relevance order and relevance clusters by part. Probing the contiguous part-number range as well took the same budget to 60 booths and 88,269 electors. It still does not reach every booth, which is why the panel says "booths found", never "the constituency's booths".
- ⚠️ ONE ROW PER PERSON, NOT ONE PER REVISION, a bug that was invisible in Odisha and glaring in Delhi. An elector holds one occurrence per roll revision. In Odisha AC 36 that ratio is 1.00; in North West Delhi it is 1.98, and flattening every occurrence published EVERY ELECTOR TWICE: the list repeated each name, the largest household was counted at 17 and again at 16, the same 102-year-old appeared twice among the elders, and the analysis reported "sampled 1,191 of 1,090", a sample larger than its own population. The latest revision now wins, because it carries the current house, age and part.
- ⚠️ A WHOLE STATE IS TOO BIG TO MEASURE LIVE, SO THE DRILL STARTS AT DISTRICT. Measured: a state-scope facet on Delhi (1.36m entries) fails at the register's own 20s gateway limit on both attempts, while the same call one level down answers in 15s and a booth in well under one. Where a state cannot be aggregated the page falls back to the district names already learned from earlier searches, with NO counts beside them, because the counts are genuinely unknown and a zero would read as "nobody lives there".
- ⚠️ EVERY PANEL LANDS ON ITS OWN. A single Promise.all made the page as slow as its slowest part: the summary answered in 0.6s and the booth enumeration in 36s, so the reader watched a shimmer for thirty-six seconds while the numbers they had asked for sat ready.
- ⚠️ FACET COUNTS ARE ENTRIES, NOT PEOPLE, AND THE PAGE SAYS SO EVERYWHERE. Delhi's U0509 sums to 1,364,769 across the gender facet against 790,266 persons. Every facet-derived figure is suffixed `_occ` all the way to the browser; mixing the two is how a district gets published with impossible turnout.
- ⚠️ COMMUNITY IS ESTIMATED FOR A POPULATION AND NEVER FOR A PERSON. Booth-level community mix is ordinary psephology and is what the vote-bank question actually needs. A religion column beside a named elector's parent, house number and booth is a different object: an addressable list of individuals sorted by religion, which is the exact artefact used to challenge voters at the booth. The classifier that produces a per-row verdict is deliberately NOT exported, so no code path outside the module can put a label on a person, and the test suite fails the build if a community, religion or caste key ever appears on a serialized elector.
- ⚠️ AND THE ESTIMATE STATES WHAT IT RESTS ON. Only low-ambiguity markers are mapped: Kaur classifies, Singh does not (Sikh, Rajput, Bihari, Gorkha); Shah, Malik and Das are left alone for the same reason. Legibility is the binding constraint, measured on Odisha AC 36, nameLatin is present on 437 of 850 rows and some of what is present ('sec harit', '3- (u') is not a name at all. North West Delhi reads 1,191 of 1,191 names and classifies 42 of them, because the local surnames are Kumar, Singh, Devi and Sharma. That is reported as 'indicative', not as a percentage standing on its own.
- ⚠️ THE MAP IS A LEGAL CHOICE. An Indian product must draw India's boundaries as India claims them. The state file was verified for exactly this: its Ladakh feature spans lon 72.53-80.33 and lat 32.34-37.08, placing Gilgit-Baltistan inside Ladakh UT and including Aksai Chin, per the Government of India map of November 2019. Natural Earth and most world datasets draw the Line of Control instead and are unusable here. Both boundary files are MIT and pinned to an immutable commit, never a branch, a moving branch would change the map with no deploy and the edge would serve it for a week.
- ⚠️ FIVE STATES DO NOT JOIN ON THEIR NAME, SILENTLY. The boundary file writes '&' where the ECI writes 'and': DELHI, ANDAMAN & NICOBAR, DAMAN & DIU, DADRA & NAGAR HAVELI and JAMMU & KASHMIR all fail a plain match, which would leave five states drawn but dead to a click, a bug that looks like a working map. Aliased and pinned by test. The constituency file is keyed on CENSUS state codes (28, 07) while the register uses ECI codes (S01, U05); `AC_NO` needs no translation and the seat counts verify, Odisha returns 147 constituencies, Delhi 70.
- ⚠️ POLLING BOOTH BOUNDARIES DO NOT EXIST, not free, not paid, not anywhere. The Commission publishes no part polygons and parts are redrawn at every revision. The map stops at the constituency and the page says why, rather than drawing a circle somebody will read as a catchment.
- No model is invoked anywhere in this page, for the same reasons as /search: the endpoint is open, every register call is billed, and responses are edge-cached on the query string for six hours. `test/electoral-voters.mjs` (66 assertions) fails the build if any outbound call leaves the electoral register.
2026-08-24
e1.2.5, Asking the register once instead of six times
- ⚠️⚠️ THE REGISTER ALREADY FOLDS SPELLING, AND THE CHECK WAS PAYING SIX CALLS A RUN TO ASK IT THE SAME QUESTION SIX TIMES. Measured live, all in `all` mode: `SANJAY KUMAR DAS` and the invented misspelling `SANZAY KUMAR DAS` return the **identical four people** in Odisha, every one of them labelled `nameFolded` by the register itself. On `SRINIVAS REDDY` the plain search and each of SHRINIVASH / SRRINIVAS / SRINNIVAS / SRINIVVAS / SRINIWAS / REDDY-first returned exactly **23,492**, the same number, six times over. The register's own matching does z/j, doubled consonants, sh/s and v/w before we ever see a row, so the orthographic variant generator buys nothing here. It stays in the file because the COURT register is typed by a clerk and has no such folding.
- ⚠️ STRUCTURAL variants are kept, because they change the QUESTION rather than its spelling: `SANJAY DAS` returns 8 where `SANJAY KUMAR DAS` returns 4, and the extra four are men the roll files without a middle name.
- ⚠️ `fuzzy` IS THE WIDEST MODE THE REGISTER HAS AND IT WAS RUNNING ON ALMOST EVERY CHECK. The gate was "no hard anchor yet", which is true whenever there is no voter ID and no name-plus-relative hit, most runs. Measured on `Srinivas Reddy`: the fuzzy wave swept **30,697 electors** against 23,492 for the plain search, and a hundred of them came back to occupy the four-hundred-row scoring cap that a common name has already exhausted. It now runs only when the ordinary waves found almost nobody, which is the case it was written for.
- ⚠️ "HOW MANY PEOPLE CARRY THIS NAME" HAS TWO ANSWERS AND EVERY REPORT PRINTED ONE OF THEM AS THOUGH IT WERE BOTH. The register matches by sound even in its strictest token mode: `Sanjay Kumar Das` is 313 under `all` and 197 under `phrase`; `Raashi Gupta` in Delhi is 267 and 82, and 185 of that difference are people called Rishi, Rishu, Reshu or Rosy Gupta. The inflation is 3% on a common name and 226% on an unusual one, largest exactly where the count decides the answer. Both counts are measured now (the six calls saved pay for it), the wider one still sets the prior because it is the population the candidates were actually retrieved from and it errs conservatively, and the report says which is which.
- ⚠️ AND THE PRIOR WAS QUOTING ITS OWN CEILING AS A MEASUREMENT. On a name with 37,058 bearers the evidence card read "About 20,000 electors carry this name", 20,000 being the clamp the arithmetic uses, not anything the register said. The measurement is quoted, the clamp is labelled a clamp.
- Gate: 158 → 164 assertions, four of them driving discovery against a stubbed register and reading back every outbound query.
2026-08-24
e1.2.4, Rishi is not a spelling of Raashi
- ⚠️⚠️ THE REGISTER MATCHES NAMES BY SOUND EVEN IN ITS STRICTEST TOKEN MODE, AND /search WAS REPORTING THE RESULT AS THOUGH IT WERE THE NAME. Reported by the founder from a live search. **Raashi Gupta** in Delhi returns **267** electors under `nameMatchMode=all`: **four** `raashi gupta`, 78 `rashi gupta` (a real spelling of it), and **185 that only sound alike**, `rishi gupta` (45, a man's name), `rishu gupta` (27), `reshu gupta` (21), `rs gupta` (17), `rosy gupta` (5), `raiesh gupta` (3). The headline read *"Raashi Gupta is a very common name on the indexed roll: 449 electors carry it"* about a name that a hundred people carry. Same class as e1.2.3, the one number that puts every other number in proportion, wrong by a factor, except that this time it arrives from the register rather than from us.
- ⚠️ AND THE OBVIOUS FIX IS WRONG, WHICH IS WHY THIS TOOK MEASURING RATHER THAN ONE LINE. `nameMatchMode=phrase` returns the exact spelling plus real variants and no phonetic matches at all (82 here, against 267) and looks like a straight upgrade. It is not. The phonetic bucket is genuinely mixed: for **Sanjay Kumar Das** in West Bengal it holds **71 × `sanjoy kumar das`**, which IS the same Bengali name and is the case this product's own golden set turns on, the o/a fold e1.2.2 was built for. And `phrase` requires ADJACENCY, so **Sanjay Das** in Odisha falls from 8 to 4 by losing `sanjay kumar das`: a middle name, not a different person. Switching the default would have cost both.
- So the mode became the reader's to choose, and the default stayed wide. **Name matching** offers *all these words* (the default: finds a middle name you did not type, and Sanjay/Sanjoy, and also the sounds-alike), *the name as written* (drops them), and *loose*. ⚠️ `nameMatchMode=any` is deliberately not offered, it returns everyone sharing any one token, which for this name is 139,895 people, most of them simply called Gupta.
- ⚠️ AND HOW COMMON THE NAME IS IS NOW COUNTED IN `phrase` MODE WHATEVER THE READER IS SEARCHING IN, one extra `pageSize=1` call, skipped when they are already in that mode. Raashi Gupta reads **106 carry it or a spelling of it**, band *distinctive*, with **343 that only sound similar** named as a separate figure rather than absorbed into the first or silently dropped. No count can separate a respelling from a different name, so the number is reported and the reader is told what it is.
- Every row already carried the register's own verdict as a badge; *name sounds alike* is amber now rather than grey, so a list where the top rows are exact and the tail is phonetic reads as what it is. The badge is quoted inline in the summary so the reader knows what to look for.
- ⚠️ Met the v7.3.7 trap again while writing the comment that explains all this: a backtick inside a comment inside a template literal ends the literal mid-sentence. `node --check` catches it; nothing else does.
2026-08-24
e1.2.3, How common a name is, is a fact about the name
- ⚠️ THE COMMONNESS BAND ON /search WAS MEASURED THROUGH THE READER'S OWN FILTERS, AND IN THE WORST DIRECTION. Found on the live page: with the father's name applied, the count behind the band was the count of the PAIR, so it read "**Sanjay Kumar Das** is a distinctive name on the indexed roll: 1 elector" about a name 313 people carry. That sentence is the one number on the page that puts every other number in proportion, a reader deciding whether a single match means anything reads it first, and turning it into a restatement of the filters they had just set inverts its meaning exactly. The band, the national count and the state and district distributions are now measured on the NAME and the spelling mode alone, whatever else is filtered, and the panels say so.
- The geography had to move for a second reason as well: the state counts are how a reader changes state, so narrowing them by the state already chosen would have left one card and no way back out.
- "1 elector carry it", the count was pluralised and the verb was not.
- A district the register prints as "PASCHIM MEDINIPUR" now reads as "Paschim Medinipur" in the results, while one it prints as "NCT of Delhi" is left exactly as it is. ⚠️ Title case is a repair, not a house style: run over everything it produces "Nct Of Delhi". A string that already carries a lower-case letter has been cased by somebody and is not touched.
- ⚠️ `test/electoral-page.mjs` WROTE ITS SCRATCH FILES TO A FIXED PATH IN /tmp, WHICH IS SHARED AND STICKY. A run under a different user leaves files the next run cannot overwrite, and the whole gate dies with EACCES on a codebase that is perfectly fine; two runs at once would have clobbered each other the same way. It uses a per-run temporary directory now.
- ⚠️⚠️ NO MODEL TOUCHES THE ROLL SEARCH, AND THERE IS NOW A GATE SAYING SO. What the reader types goes to the register as typed, through deterministic code only, honorifics stripped, a state resolved against a closed list, everything else passed straight through. The temptation to add one is local and real: the check pipeline is in the same worker and has an intake parse, a name-variants pass and a grounded district picker, any of which would look sensible bolted onto a search box. All three would be wrong here, because this endpoint is open, billed per call and cached at the edge on the query string, a model in the path makes it slow, expensive per keystroke and non-deterministic, so two readers typing the same name would stop getting the same answer. Recall on a misspelling is `nameMatchMode=fuzzy`, which is the register's own business and free. `test/electoral-page.mjs` now drives the real entry points with the network stubbed and fails if any outbound request goes anywhere but the electoral register; it was verified by injecting a Bedrock call and watching it catch it.
- ⚠️ AND ITS VERSION ASSERTION PINNED A VERSION THAT IS NO LONGER THE VERSION. It tested the rendered page for `e1.1.`, which the change log satisfies from any old entry it still carries, so it went on passing after the live version moved to e1.2.x. It now asserts the constant itself, and that the constant has an entry in the log, a report whose engine version is not in the log cannot be traced to the rules that made it.
2026-08-24
e1.2.2, A roll name is not a cause title
- ⚠️ THE MATCHING TOLERANCE WAS INHERITED FROM THE COURT ENGINE, WHERE A NAME WAS TYPED BY A CLERK. On the roll the Latin name is machine-transliterated from printed Indic text, so the variance is a choice between spellings, bijay/vijay, deepak/dipak, mondal/mandal, which the phonetic fold is built to forgive. A consonant swapped in the middle of a short given name is not a transliteration choice. Measured on fifty pairs of genuinely different, genuinely common Indian given names, the old ladder called **twenty-eight of them the same name**: Rajesh/Rakesh 0.911, Ramesh/Rajesh 0.911, Arun/Varun 0.933, Hari/Harish 0.933, Ravi/Ravina 0.933, Kiran/Karan 0.880. Jaro-Winkler's prefix bonus makes short Indian given names look alike whatever they mean.
- The ladder is now exact, then a recognised spelling variant, then a genuinely near string at 0.94, and below that a **slip**, resembles the query, is not a variant of it, worth 0.60, which is under every gate in the scorer. A far token is capped at 0.45 instead of contributing its own inflated score to the mean. After the change 25 of 25 true spelling variants still match, and the only different-name pairs still merging are the feminine -a suffixes (Sunil/Sunila, Kamal/Kamala), which no string comparison can separate and the sex field can.
- ⚠️ THE BENGALI, ODIA AND ASSAMESE O IS THE HINDI A, AND THE TRANSLITERATOR PICKS ONE. Mondal/Mandal, Manoj/Monoj, Sanjay/Sonjoy, Bimal/Bimol, Swapan/Swapon, Tapan/Tapon, Subrata/Subroto, Probir/Prabir, eight measured pairs, one name each. Without the fold a matching SURNAME lands in the slip band, trips the surname-conflict gate and kills the row outright, so tightening the ladder without this would have cost most of eastern India. Word-final ay/oy/ai/oi fold together too (Sanjay/Sanjoy/Sanjai).
- ⚠️ "I CANNOT READ IT" IS NOT "IT SAYS SOMEBODY ELSE", AND THE FIRST CUT OF THIS GOT IT WRONG. Tightening the ladder turned every mis-transcribed relative into a contradiction, the golden Odisha subject was capped at 15% for naming a different father, when the register simply prints ଚୈତନ୍ୟ ଦାସ as "chaitanya das" on the father's own row and "taitanya das" on his son's. A relative that merely resembles the one entered is now unresolved and counts neither way; a contradiction has to be a name that is genuinely different. That subject reads as a likely match on the name, the age and the district, and confirms the moment the household is opened, which is how the engine actually finds him.
- ⚠️ AND THE SAME STRING PAIR IS WORTH DIFFERENT THINGS IN DIFFERENT POPULATIONS, WHICH IS A BASE RATE AND NOT A FUDGE. Deciding which of 13,795 namesakes in a state is the subject, a resembling string is almost certainly somebody else. Deciding whether the "taitanya das" named on one roll entry is the "chaitanya das" registered in the same house, of the same sex, with a plausible parent's age gap, the field is a dozen people and three tests have already been passed. The family tree therefore compares names tolerantly; the scorer never does.
- **Age is a three-year window.** A roll prints an age, re-printed at each revision, so a year of slack is ordinary and two is common; four is a different person or a mis-keyed box. Inside three years the age supports, four to six counts against, and beyond the window an age can no longer be part of an identification at all. The old ladder paid for anything within four and then said **nothing whatever between five and eight**, which on a common name is the silence that lets geography alone carry a stranger.
- **Sex is a filter, and the register is wrong about it once in twelve.** Measured: of the occurrences under the unambiguously female name "Sunita" in Delhi, 7,518 are recorded M against 86,734 F. So a sex conflict caps a row into the excluded band, a filter in effect, but a row that the relative's name independently supports still reaches a human's review list rather than vanishing. And a search filtered by sex or age never runs alone: the plain sweep always follows it, because a filter that is the only wave is a filter that silently deletes true matches.
- ⚠️ ON A COMMON NAME THE CANDIDATE CAP IS THE SEARCH. There are 13,795 Rajesh Kumars on the Delhi roll and at most four hundred are ever scored, so an unfiltered sweep spends the whole budget on an arbitrary slice of a list the true match may not be in. Recall on a common name is not won by reading more rows but by asking a narrower question, and the register narrows by age, sex and district for free.
- ⚠️ A DISTRICT THE REGISTER DOES NOT USE IS WORSE THAN NO DISTRICT AT ALL, and the old code only ever filled a blank one, never questioned one it was given. "Central Delhi" where the roll says "Central" was sent as an exact filter (which returns nothing), counted as the district population (which read zero), quoted to the reader as "none of them are in your district", and compared against every row (which never matched). One wrong word, four wrong answers. A stated district is now checked against the district names the register itself returned, corrected where it can be, and set aside where it cannot.
- ⚠️ AND IN A CITY THE ADDRESS NAMES A CONSTITUENCY, NOT A DISTRICT. "Karol Bagh, Delhi" carries no district at all, Karol Bagh is assembly constituency 20 and its district is Central. Measured on a live report: the check ran with no district, so the district count, the district edge and the constituency edge were all dead and 13,795 namesakes were separated by the state alone. The constituency is read straight off the rows already in hand, and it brings its district with it. Where the locality entered IS the constituency name, only one of the two edges is paid, the same agreement counted twice is exactly what the damping between correlated edges exists to prevent.
- ⚠️ AND ONE NAME BEING THE OTHER WITH A WHOLE EXTRA PIECE ON IT IS THE HIGHEST-SCORING TRAP OF ALL. Hari/Harish 0.933, Ravi/Ravina 0.933, Arun/Varun 0.933, Bharat/Bharati 0.971, Reddy/Areddy 0.944, a long shared substring is exactly what Jaro-Winkler rewards, and adding a syllable to an Indian given name almost always makes a different name rather than a different spelling. Found on the live golden run: an elector whose father is plainly `Srinivas Reddy` matched the entered father `Shreenivas Areddy` at 0.94 on that alone, took a full relative anchor, scored 80, and demoted the true match, the one the register says is the ONLY elector in India carrying both names, from an identification to an ambiguity. The one place a transliterator does add a letter, the schwa in Anand/Ananda and Ramesh/Ramesha, the fold settles before this test is reached.
- Gate: 96 → 158 assertions, including every pair above in both directions.
2026-08-24
e1.2.0, Search the roll, /search
- A second way in, and a cheaper one. The check form asks "who is this person?" and spends up to a hundred and fifty register calls answering it. **/electoral/search** asks the question that comes first: who is on the roll under this name, and where are they? Name (required), relative's name, relationship, address, state, age with a tolerance, sex and loose spelling. Open to anybody, no email.
- The answer is a breakdown before it is a list. How many electors carry the name and how common that makes it; **where they are registered**, by state, one click to the districts inside a state with their own counts; the **ten-year age split**, which re-splits inside whichever place is selected; **which relationship** the roll records beside them; and the **relatives actually named** on the page of results in hand. Every count is a control: clicking one applies it as a filter, clicking it again removes it, and the active filters sit above the results as chips you can take off one at a time. The whole state is in the URL, so a search can be shared or reloaded.
- ⚠️ THE BREAKDOWNS DO NOT APPLY THE FILTER THEY ARE OFFERING. The state distribution is measured without the state already chosen, the age split without the age, the relationship split without the relationship, otherwise each panel would only ever show the reader the choice they had already made, and there would be no way to change it. Only the results table applies everything at once.
- ⚠️ A RELATIVE'S NAME IS COUNTED BEFORE IT IS APPLIED. Typing one does not filter; it asks the register how many electors of that name carry that relative, and reports it. `relativeName` is a hard filter over OCR text, the father of EPIC RHG0234120 prints as "chaitanya das" on his own row and "taitanya das" on his son's, from one Odia original, so a zero here is a zero and never an absence, and the page says which it is before the reader acts on it.
- ⚠️ EVERY NAME IN THE RESULTS OPENS THAT ELECTOR'S OWN FULL REPORT IN A NEW TAB, through the same function e1.1.2 built for the report pages. One function, because the reasoning it carries is a safety property: the new check is built from the elector's own roll entry rather than from the page clicked, and the voter ID is stored as `selected_epic` rather than `epic`, so the exact-EPIC anchor can never turn a click into a 99% identification. Search-opened reports are filed under the anonymous namespace `/v/0/...`, a user id that email derivation can never produce, so they can never be folded into a real requester's history, and no report email is sent, because there is no address to send one to.
- ⚠️ `pincode` IS NOT A FILTER ON THIS REGISTER, AND HAS SILENTLY NOT BEEN ONE ALL ALONG. Measured live: `name=Sanjay Kumar Das` returns 313 electors with no PIN, 313 with PIN 721301, and 313 with the impossible PIN 999999. It is published as a facet and as a `matchedOn` value; the capabilities list does not name it among the filters, so /search ignores it as an unknown parameter. The report page's own roll picker had been passing it as a narrowing filter since e1.1.0, the reader typed a PIN and the same list came back looking narrowed. Removed there, never offered here. `includeDeleted=false` measured as a no-op too; deleted entries are labelled in the results instead.
- ⚠️ A COLD QUERY FOR A COMMON NAME TAKES TWENTY SECONDS. "Ramesh Kumar" (24,947 electors) measured at 21.0s for one page of rows and 11.0s for the state facet against a cold backend, then 1.0s and 1.4s once warm. The 12s timeout that is right for the report pipeline would turn every first search for a common name into a reported outage, so `elSearch` takes a timeout now and this page passes 25s. It also splits its work into two independent requests, the results table and the breakdown, fired together and painted as each lands, so a slow facet can never hold up the list.
- ⚠️ THE ENDPOINT IS OPEN AND EVERY CALL IT MAKES IS BILLED, SO IDENTICAL QUESTIONS ARE ANSWERED ONCE. `/api/electoral/rollsearch` is cached at the edge for five minutes on the whole query string, which is exactly the identity of the question. Only a 200 is cached: a 502 from a cold register must not be served back for five minutes as though it were the answer.
- ⚠️ THE ADDRESS BOX APPLIES A STATE AND NOTHING ELSE. A state name resolves against a closed list, so it is safe to apply. `districtName` is a real filter, which means a wrong guess does not error, it returns nobody, and "nobody" reads to a reader as "this person is not enrolled". So the rest of the address is kept as a word and highlighted in gold against the district counts once they arrive: a suggestion the reader clicks, never a filter applied behind their back.
- The district facet answers in codes (`S2512`) and there is no districtName facet, so new table `el_place_names` learns a name from every row the page reads and is consulted before any extra call is spent. Codes it has never seen get one tiny lookup each, capped at eight, four at a time; the rest show their code this time and are named the next time somebody searches that state. ⚠️ The schema probe in electoral-routes.js now asks after this table, not after `electoral_checks.parent_code`, a probe on the older object succeeds on every database that predates the newer one, and a warm isolate would have skipped the CREATE for the table it was about to read.
- The age filter is approximate and says so. There is no ageBand filter, only an ageBand facet, so a band is expressed as age plus or minus a tolerance, and the register works from the year of birth while each entry prints the age recorded at its own revision, a 38-47 band legitimately returns a row printed as 37. The arithmetic lives in one place, `ageFromBand`, and travels to the browser inside the facet rather than being recomputed there.
- Each result carries what the register says it matched on, name exact, relative exact, age in range, state, so a reader scanning a common name can see which rows are hits on more than the name. Deleted entries are labelled, and where an elector appears on several revisions the row shows the most recent one still live.
2026-08-24
e1.1.2, The name is the thing you click
- ⚠️ A BUTTON THAT REBUILDS THE REPORT YOU ARE READING IS THE WRONG SHAPE FOR THIS. Choosing a person now opens THAT PERSON'S OWN REPORT in a new tab, under its own reference, leaving the report you came from exactly as it was. Every name in every list is the link, in the candidates, the household, the neighbours, the polling station, the namesake tables, because the name is what is being chosen, so the name is what you click.
- ⚠️ AND THE NEW REPORT IS BUILT FROM THAT ELECTOR'S OWN ROLL ENTRY, NOT FROM THE ONE YOU CLICKED FROM. Inheriting the first subject's name would file one person's report under another person's name, and the name is what every namesake count, every spelling variant and the whole prior are computed from. The register is asked who that voter ID is, and the answer becomes the subject of the new check.
- ⚠️ THE VOTER ID IS RECORDED AS A SELECTION, NOT AS A DETAIL THE REQUESTER SUPPLIED. Writing it into the new check's `epic` field would let the engine's exact-EPIC anchor confirm the row unaided and publish a human's choice as a 99% identification. It goes in `selected_epic`, which forces the identity only after every candidate has been scored and labels the report as the requester's choice throughout. Clicking a name twice returns to the report already built rather than running the same person again under a second reference.
- **People with the same name**, **The houses either side** and **ECI Poll & Polling station Details** are collapsed sections now, like the research block, the report opens as a readable page rather than a scroll. The same-name section opens by itself when nobody was identified, because a reader told that nobody could be identified must not then have to go looking for the list of people it might have been.
- ⚠️ THE DISTRIBUTION CARDS ARE THE FILTER, AND THE SEARCH BOX IS GONE. They were already the clearest thing in that section, where the name concentrates, in counts, so they now select: All of India by default, click a state and the list below becomes the electors of that name there, with a district dropdown (the districts that name is actually registered in, with counts) to narrow further. Where the distribution facet came back empty there are no cards to click, so a plain state list stands in; a reader with no way to look anywhere else is worse off than one with a dropdown.
- Sex reads M or F in the lists. A column that says "Male" thirty times is thirty words nobody needed.
- Gate: the page suite now asserts the collapse structure, that the ECI section holds the station and the roll timeline, that the same-name section holds the no-match line and both chooser shapes, that a name links to /identify/ and opens in a new tab, and that no list spells sex out.
2026-08-24
e1.1.1, The report reorganised around the questions people actually ask of it
- **ECI Poll & Polling station Details** is now one section, and it holds everything the Election Commission files a person under: the polling station, the assembly constituency, the section of the part, the part number and the serial that locate their line on the printed page, the roll revision, then the address history across revisions, then the profile of everyone who votes at that station. Those facts were previously in three places: half of them squeezed into the identity card, the roll history alone under its own heading, and the booth profile collapsed at the foot of the page.
- ⚠️ THE IDENTITY CARD IS ABOUT A PERSON AND AN ADDRESS, NOT ABOUT A POLLING BOOTH. It now carries the name, relative, sex, age, roll, house, district and PIN and stops there, with a line pointing at the section that has the rest. The part and serial are not identity, they are filing.
- **The houses either side** is a section of every identified report now, opened rather than collapsed. Roll parts are printed house by house and serials run in address order, so the entries around this one ARE the neighbouring dwellings, and in a compound they are usually the rest of the family, which is most of what a reader is trying to establish. Where the neighbouring houses could not be read, the section says so; leaving it out reads as "there was nothing there" when the truth is "this was not reached".
- ⚠️ "NO ELECTOR IDENTIFIED" WAS A HEADING ANNOUNCING AN ABSENCE, THREE SCREENS AWAY FROM THE LIST OF PEOPLE IT COULD HAVE BEEN. It is now the opening line of **People with the same name**, which already carried the counts and the state-by-state distribution that explain the absence, and the picker sits directly beneath them. One subject, one section, in the order a reader meets the question.
- ⚠️ THE PICKER'S FREE-TEXT SEARCH IS GONE, AND ITS REMOVAL IS A SAFETY FIX RATHER THAN A SIMPLIFICATION. A name box on a finished report invites somebody to point it at a different person entirely while keeping the first person's reference, findings and audit trail on the page, the exact confusion this product exists to prevent. The name is now taken from the check itself and never from the request. What the reader may vary is WHERE to look: choose a state, and the districts that name is actually registered in are listed with counts (one facet call), narrow by district or PIN code if you know it, and pick from the people. A different name is a different check, and the panel says so.
- ⚠️ AND THE DISTRICT FACET ANSWERS IN CODES, NOT NAMES. `U0509`, not `South East`, there is no districtName facet, checked against the register's own published capability list. Rows carry both fields, so one page of results is read beside the facet purely to put names to the codes, and a code no row named is still offered under its code: the filter is the code either way, so a missing label costs a nicer word and nothing else.
- The voter ID column is off the pick tables. It is the one field on the row nobody can check anything against, you do not recognise your tenant by their EPIC, and it pushed the fields that DO separate people off the edge of a phone screen. The number still travels on the button, because that is what the register is asked with.
- Gate: new `test/electoral-page.mjs` assertions on section placement, the card must not carry the station, the station and the timeline must be inside the ECI section, the houses either side must exist on every identified report, the no-match line and the picker must be inside the same-name section, and the picker must offer no way to search for a different name.
2026-08-24
e1.1.0, When the register cannot tell two people apart, the reader can
- ⚠️ THE ENGINE'S HONEST ANSWERS WERE ALSO ITS DEAD ENDS. An unresolved verdict, nobody identified, or several electors matching equally well, was the end of the report. But the person asking very often knows the one thing the roll does not carry: which of these four Rajesh Kumars is their tenant, their borrower, their client. The report described the ambiguity accurately and then gave them nowhere to put that knowledge.
- Every report now carries a picker. It lists the electors this run screened, with the fields that actually separate them side by side, the relative named, the age, the house, the part and serial, the constituency, the voter ID, and a live search of the roll beside it, so a picker exists even on a run that returned nothing at all to score. Choosing one runs the whole household, family, neighbour and polling-station build around that person and publishes it as a fresh evaluation, leaving the first report intact.
- ⚠️ A REQUESTER'S CHOICE IS NOT EVIDENCE, AND THE REPORT MUST NEVER LAUNDER IT INTO EVIDENCE. This is the rule the whole feature is built around. The chosen entry keeps the score and the band the engine gave it, a 41% row selected by hand publishes as 41%, the identification is labelled as the requester's throughout the page, the JSON and the emailed summary, and the electors that matched equally well are still named. Nothing about the selection reaches the scorer: the choice is applied after every candidate has already been scored, so it cannot lift a row and it cannot silence a rival.
- ⚠️ AND THE CHOICE CANNOT BE MADE BY SOMEBODY WHO SHOULD NOT SEE THE REPORT. The selection endpoint is authorised the way every other route on this product is, by the reference and the user id in the report's own private link, and it will only accept an elector the register actually returns for that voter ID. A selected entry that the roll cannot confirm is refused rather than rendered.
- ⚠️ FOUND ON THE FIRST LIVE RUN OF THIS FEATURE, ON `Rajesh Kumar` s/o `Ram Avtar` in Karol Bagh, 40 same-name electors, five of them scoring 81 to 94, nobody identified. The report told the reader **"0 other electors match the details entered about as well as the one reported"**, because the ambiguity rule had demoted the leading row out of the identified band and the count was being read off the row that no longer existed. The count belongs to the run, not to the winner. Also new: the engine's own leading row is now recorded against a hand-picked report even when the engine identified nobody, which is the usual case, and is exactly what somebody choosing by hand wants to see.
- ElectoralCheck now has its own version log at /version, its own version in its own top bar, and its own history of what changed. The first four entries below are backfilled from the court log, where they were recorded at the time.
- Gate: 79 → 96 assertions, thirteen of them on the selection path, a hand-picked row may not gain a point, may not gain a band, may not clear the ambiguity note, must carry the requester's provenance into the JSON, and an unknown voter ID must be refused.
2026-08-24
e1.0.3, One person, two voter ID numbers
Recorded after the fact. Shipped under court engine v7.7.3. The electoral constant was not bumped, because this log did not exist yet.
- ⚠️ DUPLICATE ENROLMENT IS ORDINARY IN INDIAN ROLLS, AND THE ENGINE READ IT AS A SECOND PERSON, TWICE OVER, IN TWO DIFFERENT SUBSYSTEMS. Found live on `Rachit Jain`: the same man, born 2005, father `Vipin Jain`, house P701, entered under two different EPICs. The family tree made the second entry his BROTHER. The identity scorer saw a candidate matching the subject exactly as well as the subject did, and flagged the whole report ambiguous, against itself.
- Both readings are now closed by the same test: same household, same sex, year of birth within one, near-exact whole name, matching relative. ⚠️ It is deliberately strict, because the failure on the other side is merging actual TWINS, who share a house, a father, a surname and a year of birth. What twins do not share is a given name, so the whole name must match and the household must be the same one.
- ⚠️ AND IT IS A FINDING, NOT ONLY A FIX. That an elector appears on the roll more than once is worth telling the reader: it is precisely what a roll revision sets out to find, and it explains an address history that otherwise reads as two people. The report names every voter ID the person holds, with the part and serial for each, and says plainly that this is a fact about the register rather than about the person.
- Gate: 76 → 79 assertions, including the twins case and a same-name elector at a different house, both of which must stay separate.
2026-08-24
e1.0.2, A confirmed stranger, found by running the golden set
Recorded after the fact. Shipped under court engine v7.7.2. The electoral constant was not bumped, because this log did not exist yet.
- ⚠️ RUNNING THE COURT ENGINE'S 14 GOLDEN SUBJECTS THROUGH THE ELECTORAL CHECK PRODUCED A CONFIRMED FALSE POSITIVE ON THE ONE SUBJECT THAT EXISTS TO CATCH THEM. `Rajesh Kumar` s/o `Ram Avtar`, Karol Bagh, came back IDENTIFIED at 99%, with three other Rajesh Kumars whose fathers are also Ram Avtar sitting at 99, 95 and 92 in neighbouring districts. Four bugs stacked to produce it, each harmless alone.
- ⚠️ AN UNMEASURED POPULATION IS NOT A SMALL POPULATION. The national count call failed and returned null; the prior fell to its 500 default and the common-name ceiling, which reads the same field, switched off entirely. The state count had succeeded and said 13,795 in Delhi alone. A failed measurement now falls back to the widest number that did arrive, and where nothing arrived the name is assumed COMMON, that is the assumption whose failure is survivable. A measured ZERO is separately not a population of one: it means the name as typed is absent and the row came through a variant wave, so the prior is set to a deliberately middling figure rather than to near-certainty.
- ⚠️ AN AGE IS NEVER AN IDENTIFICATION ON A COMMON NAME. An exact age match marked the row `hard`, and `hard` is what switches off the common-name ceiling. Any year of birth is shared by roughly one adult in fifty, so among 13,795 Rajesh Kumars some 275 were born in 1985, and one of them was published at 99%. Age corroborates and no longer identifies unless the name is uncommon.
- ⚠️ A HOUSEHOLD CONTAINING A COMMON NAME IS ARITHMETIC, NOT EVIDENCE. The second-relative-at-the-same-address anchor shipped flat at x120 and hard. `Ram Avtar Sharma lives at the same house` sounds decisive until the name is measured at 1,717 bearers in Delhi. It is now laddered by that measurement exactly like every other name in the engine, and is hard only below 1,000.
- ⚠️ EVIDENCE THAT FITS SEVERAL PEOPLE EQUALLY HAS IDENTIFIED NONE OF THEM. A rival within ten points now demotes the verdict, one drops a confirmation to a likely match, two or more drop it to unresolved, and the rivals are named in the banner and in a table beside the entry, not buried in the limitations. Only an exact voter ID is exempt, because an EPIC identifies one enrolled elector by construction.
- Recall, from the same sweep: `Vassant Tukaram Madkaikar` scored 55% against `vasant madkaikar` s/o `tukaram madkaikar`, the same man, with the patronymic moved one column across, which is how the register writes names through Maharashtra and Goa. A middle token the ROW ITSELF accounts for in the relative field is no longer counted as unmatched. Absorption requires the relative to match and may only ever raise a score, so it cannot talk a stranger up. 55% → identified.
- The national name count is retried once, with a longer clock, because it sets the prior for every row in the report and on a common name it is also the slowest call in the run.
- Gate: 68 → 76 assertions.
2026-08-24
e1.0.1, The check stops grinding when the register stops answering
Recorded after the fact. Shipped under court engine v7.7.1.
- ⚠️ FOUND ON THE FIRST REAL CUSTOMER RUN, HOURS AFTER LAUNCH. The partner REST API began answering `service_unavailable` ("A service did not answer in time") on some paths and hanging outright on others, while the MCP connector to the same backend stayed healthy. An ElectoralCheck run met it and ground through search after search, each burning the full 30-second timeout, and sat in `running` for the best part of an hour with nothing to show the requester but a live feed repeating the same failure.
- A circuit breaker now opens after five consecutive failed calls with no success between them, and every later call returns immediately. One failure is counted per LOGICAL call and the streak resets on any success, so a single unlucky call cannot trip it, the court engine tripped its own breaker after barely two bad calls by counting inside the retry loop.
- ⚠️ NEVER PUBLISH "NOT FOUND ON THE ROLL" ON SEARCHES THAT NEVER RAN. This is the court engine's v7.0.19 doctrine translated, and here the dangerous output sounds worse: a report saying nobody of this name could be identified, when the register was simply never asked. A run reaching the end with zero successful searches now raises a retryable error instead of rendering, and a breaker that opens part way through with nothing identified does the same. Partial failure is survivable and is stated as a limitation, with search confidence capped at 25.
- The per-call timeout drops 30s to 12s. A healthy electoral search answers in 200-900ms and the slowest legitimate call measured was 15s, on an unscoped sweep the engine no longer issues. At 30s a dead upstream cost 150 x 30s before anyone found out; at 12s an outage is half a minute of honest failure.
- Gate: 68 assertions, eight of them driving the collector and the pipeline against a stubbed-dead upstream, the breaker opens, stops spending calls, says so once in plain language, resets on a single good call, and the pipeline refuses to render.
2026-08-24
e1.0.0, ElectoralCheck, a second product at /electoral
Recorded after the fact. Shipped under court engine v7.7.0.
- New: ElectoralCheck at legalcheck.ecourtsindia.com/electoral, built on the electoral-roll API (/api/partner/electoral/*) rather than the case index. It identifies one elector out of everybody who shares a name, then builds the household, the family tree, the neighbouring houses, the polling station and the namesake distribution around them.
- ⚠️ `relativeName` IS A HARD FILTER OVER OCR TEXT AND MUST NEVER BE THE ONLY WAVE. The father of EPIC RHG0234120 prints `chaitanya das` on his own row and `taitanya das` on his sons' rows, from the same Odia original, so name + relative returns zero for a man plainly on the roll with his father beside him. He is found by the inverse route instead: search HIS name, open HIS household, and look for the subject inside it. His name has to be legible; the subject's does not.
- ⚠️ WEIGH THE RELATIVE BY THE PAIR, NOT BY THE NAME. `Srinivas Reddy` s/o `Shreenivas Areddy` in Rangareddy has 36,820 name-bearers and 6,673 father-name-bearers; the commonness proxy called the father very common, paid x5, and left the one true match at 19%. The register will state directly how many electors carry BOTH names: one. Among N bearers with k matching relatives the likelihood ratio is N/k. Same subject: 99%.
- ⚠️ INSIDE A HOUSEHOLD THE SURNAME IS FREE. Everyone at house 177 is a Das, and a mean-of-best-tokens comparison scored `Rakhal Das` against `Gouranga Das` at 0.75, which once resolved the subject's grandmother as his cousin's wife. The given name now gates every comparison in the tree and in the relative scorer.
- ⚠️ COVERAGE IS A DISTRICT QUESTION, NOT A STATE QUESTION. A state probe on Maharashtra returned 1,475 rows against Delhi's 770,973 and reported "loaded", while the subject's own district held no roll at all, so "not found" would have been read as a finding.
- The family tree resolves structurally, right sex, plausible parent/child gap, same household, with name similarity applied last and weakest, which is why a mangled transliteration costs nothing. A relative named but not enrolled becomes a ghost node with real evidence and no record, and an inference drawn across neighbouring houses is drawn dashed and labelled, because it is a reading of a compound rather than something the register says.
- Own D1 tables, created by the code itself on first request: the deploy is a git push and nothing in that path runs SQL, so a migration step here is a step that gets forgotten and then 500s a live product.
- Gate: test/electoral-calibration.mjs, 60 offline, deterministic, free assertions over folding, likelihood ratios, veto caps, family inference and tree layout. Every one of them is a decision the engine got wrong at least once.
Run a full check · Report history