and the measurement turned up finding 17, a live scoring defect — which was then FIXED on 2026-08-11 with the corpus reload it needed (ADR-97, scripts/remeasure_alignment.py, 333 changes read, 25 moved, 2 cautions lifted, 0 added).
The design was also incomplete and an outside reading caught it, which is the more useful half of this row. Everything designed here was a way to survive more work — a queue, a pool, an admission check — and nothing reduced the work in a single pair. Section 3 had already measured that normalize() is 87% of the diff call and that a version takes part in many diffs, and neither the design nor the review that passed it asked why that cost is paid more than once. Section 4b now carries the answer: hash each normalised passage at ingest, anchor on hashes unique to both sides, align only the gaps. Measured on the Kentucky pair, identical output, 2.36x — and the naive form, hashing at diff time, is 0.65x, a loss, because hashing means normalising and normalising is the 87%. Still nothing is built: it needs a hash column and a derived-corpus rebuild.
Recompiled 2026-08-07. Every row below was re-checked against the code on that date, not carried forward from the previous version of this page. The first version was compiled 2026-08-04; 64 commits have landed since, and most of what it listed is closed.
docs/web-design.html gained a
part five saying which of its earlier parts now describe a surface that no longer exists.Empty. All six closed. Listed at the foot of the page.
Worth stating plainly rather than leaving as an absence: the repository is public
(gh repo view jsahasi/verbatim --json visibility returns PUBLIC), the
working tree is clean with nothing uncommitted, make test passes on a fresh clone, both
routes that render review_project.html answer 200, permissions is in the
nginx proxy list, and make run seeds the eight duties because
scripts/seed_demo_gaps.py is now called by both the seed target and
deploy/entrypoint.sh.
Run rather than recalled, on 2026-08-07 at commit 65c42d7:
make fresh-check cloned HEAD into a temporary directory, ran the suite there and exited
zero — 2,340 passed, 1 xfailed in 327s, "fresh clone: tests pass".
make test in the working copy gives the same result in 325s. That is the command a
reviewer runs, run the way a reviewer runs it.
All four items from 2026-08-04 are closed. The three public-site pages that made claims the code
had reversed — security.html on whether a model call runs on the request path,
subprocessors.html and privacy.html on whether Anthropic is a
sub-processor, security.html again on which surface the safe harbour covers — now say
what is true, and subprocessors.html keeps a paragraph recording that it used to argue
the other way. The six admin screens that were reachable only by typing the URL now sit behind a
permission-gated Admin item in the masthead. One new item takes their place.
| Gap | What it says, and what is true | State |
|---|---|---|
| P1-5 The test count is 39 behind in four documents |
make fresh-check at HEAD (65c42d7) prints 2,340 passed, 1
xfailed and exits zero. Three documents carry 2,301 passed, 2 xfailed across five places:
submission.html at lines 246, 1035 and 1052, tech-questions-faq.html line
58, interview-bank.html line 60.
These are not dishonest — every one is stamped with the commit it was measured at
(a5c20cb, 3ab55b7, bd21a5a), which is this project's own
convention for a dated snapshot. The problem is narrower and worth fixing anyway:
submission.html line 246 introduces the figure as "the reviewer's command, run as a
reviewer runs it", and a reviewer who runs it gets a different number in the first minute of
reading. No test file changed between 05b05e0 and HEAD, so the drift is older than the
stamp suggests. The cost is a reviewer's first impression; the fix is one re-run and five
edits.
Update 2026-08-07: docs/.ai/briefing.html was a fourth and staler case — it carried 2,006 passed at commit 68cbfa2 — and has been re-verified against the tree and corrected, along with 21 other claims. The three documents above are untouched and this row stays open for them.
Closed 2026-08-08: make fresh-check was re-run at commit ba5965b and printed 2,353 passed, 1 xfailed in 307 seconds, exit zero. All five places now carry that figure, each stamped with this date and that commit: submission.html in three places, tech-questions-faq.html, interview-bank.html. resume.md was a sixth place this row had not counted, and it is corrected too. The line in submission.html that introduces the figure as "the reviewer's command, run as a reviewer runs it" now matches what a reviewer running it gets. |
CLOSED 2026-08-08 at ba5965b. |
| Gap | Why it matters | State |
|---|---|---|
P2-3start_run is called by nothing |
Narrowed from four items to one. propose.py now has real callers —
materiality_for_company from changes.py:1014 and
shown_materiality_for_company from projects.py:443 and
chat/tools.py:1103 — so the interpretation gate and the thing it gates are connected.
can_approve has its caller in app/web/views/actions.py:495 (ADR-91).
revert_event has one in app/state/replay.py:704. That leaves
start_run in app/state/workflow.py:1744, exported in
__all__ and reached from tests/ only: no approval run starts from any
screen. No ADR covers it, which is the part to fix first — a gap with reasoning written down is a
different answer at a panel than a gap without. |
CLOSED 2026-08-10. start_run has a caller: POST /escalations/{id}/route, actor from the session. |
P2-4record_note is gated on an approximate code |
Narrowed. It is no longer ungated: PERMISSION_NOTE_WRITE = "knowledge.write" in
app/chat/tools.py:249, and the comment above it says in full why that is the closest
code the product defines rather than the exact one. The exact code, note.write, is
still not defined, so the grid gives a note-writer everything knowledge.write covers.
The reasoning is written down, which is the difference between this and where it started. |
CLOSED 2026-08-10. note.write defined and granted to the analyst and the obligation owner; the alias is gone. |
| P2-7 A source status question, deliberately deferred |
Unchanged and still deliberate. FETCHABLE_SOURCE_KINDS is empty, so
app/web/views/integrations.py answers no for every kind. Setting it to name the public
docket moves a registration that names no document from not_implemented to
never_tried — different sentences to a reader, and four tests assert the first. Neither
fits a row whose registration is merely incomplete. Written down rather than answered in a hurry. |
CLOSED 2026-08-10 as examined, not built. No live defect; the hazard is guarded. |
| P2-14 Nothing tests that the site's palette and the application's agree, and the obvious test would lie |
deploy/site/site.css mirrors verbatim.css by hand, token by token, each
naming the line it copies. It does not import it, and it must not: the site is allowed a mesh on its
chrome and the application is not allowed one anywhere
(ADR-94). Hand-mirroring with no test is exactly the
condition four token-family defects arrived under during the redesign.
The warning matters more than the gap. The obvious parity test — read
--accent from one file, --stamp from the other, compare — would be
wrong, because the two files do not share a scheme structure. verbatim.css answers
prefers-color-scheme: dark and then prefers-contrast: more three times, once
plain and once for each scheme; site.css answers dark once and
prefers-contrast: more once, undivided. A last-occurrence lookup therefore compares the
application's high-contrast-dark value against the site's dark value
and reports a mismatch that does not exist — a red suite for a correct pair, which is worse than
no test, because it gets deleted rather than fixed. Any test here has to parse the scheme each
declaration sits in and compare like with like. |
CLOSED 2026-08-10. Checked against the mapping site.css declares on every line, both schemes, mutation-checked. |
| P2-15 The gradient guard reads one of the two stylesheets |
tests/test_design_guards.py::test_no_record_surface_carries_a_gradient reads
app/web/static/verbatim.css and nothing else. The sibling CDN test sweeps both sheets and
every page; this one does not. So the site's own version of the rule — that a mesh may only ever
sit on chrome, and never on .rec, .withheld, .mismatch,
.stage, .note or .srctext — is held by a comment at
deploy/site/site.css:356. That is the state ADR-94 was written against, in
the file where the redesign put the hero refusal: the site is the surface with actual gradients on it,
so it is the one where a wash could land behind evidence. The fix is the same check pointed at
site.css with that file's own selector list and its own exemption for the hatch in the
empty statement slot, which is argued for in place. |
CLOSED 2026-08-10. The guard reads both stylesheets and is parametrised over them. |
| P2-16 The 62rem bound on the table scroll rule |
Above 62rem a table stops being its own scroll box, so a register with more columns than a laptop
can hold pushes the whole document sideways. Two do: /admin/sources ran
documentElement.scrollWidth to 1368 and /admin/invites to 1377, both against a
clientWidth of 1280, measured in a browser rather than read off the markup. Lifting the
bound fixes both and shrinks seven other tables off the right edge — the two on a proceeding go
from 1168px to 565px and 499px — because a block table shrinks to its columns instead of filling
its row. So the treatment is named .table-scroll and worn where it is needed, and
verbatim.css:4121 says in capitals that the bound is a hole and not a decision:
a table is too wide when it is wider than the space it has, which is a fact about the table and not
about the screen. Recorded here so it is not read as settled. |
CLOSED 2026-08-10. Every 6+ column register wrapped, guarded, mutation-checked. Was written down in the stylesheet as open. |
| P2-19 The changes screen labels a testimony line number as a section, and fixing it is a corpus migration |
app/ingestion/ingest.py::_section_of takes the leading integer of a passage and writes
it to Passage.section, which the changes screen renders as Sec 193 when 193 is
the line number printed down the left of a deposition page. It is the same defect
ADR-102 measured on the forward path: the leading integer
fires on 39.7% of the 107,122 passages in data/real and returns 2025,
207 and 480 among its labels. The forward path was measured and the tree that
would have fixed it was withdrawn; the backward path was never touched. Why it is not a line
change. Passage.section is stored, so correcting the rule re-labels every passage
of every ingested filing — a derived-corpus migration under best-practices §27, all at once,
with the store rebuilt, not rule by rule against a half-old table. The gutter detector in
app/diff/structure.py is the piece that would carry it: it flags 19 of the 102 filings and
every one is direct or rebuttal testimony. Recorded here because ADR-102 says it is, and a decision
that names a gap nobody filed is a decision with a false sentence in it. |
CLOSED 2026-08-19, and it did need a reload rather than an edit. app/text/gutter.py now holds the line-number detector, so app/ingestion/ingest.py can ask the same question the parser asks without importing it — ADR-102’s unwiring is intact and there is one implementation, not two. _section_of refuses a BARE leading integer in a line-numbered document and still reads “SECTION 4.” and “4.4”, because a gutter never writes those. scripts/relabel_sections.py carried the corpus: 105 versions, 107,257 passages, 21,281 passage labels and 261 change labels moved, 17,524 of them cleared a line number and now carry none. Both tables in one transaction, idempotent on a second run, and zero bare-integer labels remain inside the 19 transcripts. |
| Gap | Why it matters | State |
|---|---|---|
| P2-13 The film shows the product before the redesign, and its poster still carries the teal mark |
deploy/site/verbatim-demo.mp4, verbatim-demo.webm and
verbatim-poster.jpg were recorded on 2026-08-05 in a59be0c — the commit
whose message is "The film on the landing page is the film the product now is". That sentence went
false on 2026-08-07, when the palette moved to indigo, the masthead was rebuilt and the display face
landed. The poster frame is the worst of it: it is the still a reviewer looks at before
pressing play, on a page whose every other pixel is indigo, and it shows the old teal mark.
scripts/film.py was recoloured with the rest of the six painted places and now names
#2f4bd8, so re-rendering is the fix and nothing else is in the way. This was scoped
out of the redesign deliberately — re-recording needs the whole seeded walkthrough driven
again and it is not a stylesheet change — and it is recorded here rather than left unsaid, because
a film that contradicts the page around it reads as a product screenshotted at two different times. |
CLOSED 2026-08-10. Re-recorded from scripts/film.py against the redesign; 42 shots, 129s. See also the reviewer's order. |
| P2-17 Half the real corpus reaches no screen |
data/real/ holds 102 documents across eight jurisdictions. scripts/ingest_real.py
and scripts/seed_real_findings.py between them load four: Georgia, Kentucky, Missouri and Utah.
Indiana, North Carolina, Ohio and Virginia are never ingested — 52 files, more than half the
corpus, on disk and in no database. Nothing is broken and nothing fails: the loaders name the
dockets they load and load them correctly. The documents were simply gathered and not wired up.
Two costs. The counts this project quotes are true of the corpus and not of the product: a reader who hears "102 real filings across eight jurisdictions" reasonably infers the product has read them, and it has read fifty. And the Indiana set is the whole lifecycle of IURC cause 46258 — AES Indiana's verified petition, Spanos and Davis-Handy direct testimony, three OUCC exhibits, the settlement motion, the final order and the OUCC's rehearing petition. A contested $192.9M rate case, start to finish, sitting unread by the product it was collected for. |
CLOSED 2026-08-10. 102 of 102 read; scripts/ingest_dockets.py, 19 dockets. |
| P2-18 Nothing evaluates the model path |
make eval scores five metrics and passes all five, and its own caveat says why that is
narrower than it looks: "No model runs here and no network call is made, so nothing in this scorecard
says anything about model behaviour. The model path is evaluated separately or not at all." It is not
at all. There is no eval on judge_materiality, none on the assistant in app/chat/,
and none on the answerer added with /explain. What exists instead is one live transport probe
from 2026-08-05 proving the wire format works once.
The scorecard is honest about this and says so unprompted, which is why it is a P2 rather than a P1. It becomes the blocking item the moment a customer's own documents reach materiality judgement. |
CLOSED 2026-08-10 with I3. app/evals/model.py and two workflows.
make eval still prints five metrics and still opens no socket, which is the reason the
model path got its own harness rather than a sixth metric. ADR-101. |
| P2-8 The demo route lets the admin approve their own route |
scripts/seed_route.py still sends STP-2 "Legal review" and STP-4 "Officer signs the
filing" to role:admin, so the account that draws the route approves through it twice —
the exact segregation-of-duties failure this product exists to surface, in the demonstration a panel
opens. The choice is still open: fix it, or make it deliberate and let the conflict
report catch the demo's own configuration, which is a stronger demonstration. Leaving it accidental
is the only bad option, and three days later it is still accidental — nothing in the script's
docstring claims it. |
CLOSED 2026-08-10, and it was not the defect this row described. See the entry. |
docs/user-research.html, which still
records 45 messages and zero interviews. Two of the five rows are narrower than the interview stated,
because the code already does part of what it asked for; both say so.| Gap | What it is | State |
|---|---|---|
| I1 The scalability strategy is a constraint list, not a design |
Verbatim: "the scalability strategy for processing 10,000 concurrent multi-million-word documents
remained high-level, primarily noting memory and CPU constraints." That is a fair reading.
app/diff/engine.py runs SequenceMatcher in process over two whole passage
lists, and app/pipeline.py walks them one pair at a time. Nothing partitions a document,
nothing bounds memory against its size, and there is no queue, no worker pool and no backpressure
anywhere in app/. None of that is a defect at this size — 19 dockets
and 105 versions run in seconds — and it is exactly the question a procurement conversation asks
first. Answering it needs a written design, not code.
Answered 2026-08-10, and narrower than the interview stated. docs/scalability.html
carries the curve measured by scripts/bench_diff.py, and section 4 designs the pool, the
queue and the admission check. The premise turned out wrong on this codebase: on a 3.29-million-character
pair, normalization is 87% of the call and the list-level SequenceMatcher is 1.5%, so anybody
who reads "SequenceMatcher is quadratic", opens the engine and starts tuning the matcher spends a day and
finds nothing. Partitioning was measured rather than argued — index partitioning turns the real
Kentucky pair's 144 changes into 777, content anchors reproduce the answer and run slower, and neither
helps the quadratic case. The quadratic is the price of autojunk=False, which is a correctness
setting: turning it off reports 150 changes where 2 occurred, 149 of them pairing a passage with a passage
carrying the same text, each with a citation that verifies.
ADR-96. |
ANSWERED 2026-08-10 as a design, and nothing is built. No pool, no queue, no admission check; the size of the corpus is what keeps this honest, and that is not an argument. Two things stay open beneath it: the binding limit is one bad document rather than ten thousand of them, and the measurement turned up finding 17 — a live scoring defect held open on purpose by ADR-97 until a corpus reload can carry the fix. |
| I2 No truncation policy, because there is no multi-turn context yet |
Verbatim: "the LLM helper's context management and token truncation strategies for long user
sessions were not fully defined." Narrower than stated, and worth being exact about.
There is no long session to manage. app/chat/engine.py accepts a history
argument and deliberately ignores it, and says why at length: "Passing prior turns back into the model
is a real feature and a real decision ... and inventing it inside a shim would be smuggling in a behaviour
nobody chose." app/explain/answerer.py sends a single user message and keeps nothing.
So today's context is bounded by construction and there is nothing to truncate.
The gap is the inverse of the one named: multi-turn is unbuilt, and the policy has to be designed before it is built, not after. The interview's own next step is the right policy and has no code to attach to — "prioritizes retaining the deterministic diff offsets over older conversational turns." A citation offset dropped to make room for chat is a claim that stops verifying, which is the one failure this product exists to prevent. Built 2026-08-10. The rule the interview named is a module and a tripwire rather than a sentence. app/chat/budget.py holds one ordering — the question, the citation spans and
the diff offsets are the floor and are never evicted; turns go oldest first and contiguously — and
when the floor alone will not fit it refuses the turn rather than shaving a citation to fit.
tests/test_history_needs_a_budget.py walks the syntax tree of app/ and fails the
day a value derived from the conversation reaches a model call without going through it, which is what
makes it a rule rather than a preference. ADR-98,
ADR-99,
ADR-100. |
CLOSED 2026-08-10, as far as the gap goes. Multi-turn is still unbuilt and deliberately so, and the policy is now written before the feature rather than after it. Three things stay conceded on the page and in the ADRs: the budget is measured in characters, not tokens, because no offline tokenizer is a dependency and a ratio would be invented; a single turn still grows across up to four tool rounds with per-tool caps and no total governing them, which is a second budget nobody has written; and the tripwire matches by name, so a rename walks past it. |
| I3 The eval is not a gate |
Verbatim: "build a test harness that runs the fabrication and miss rate assertions against a golden
dataset of historical filings on every commit ... a CI/CD pipeline that automatically blocks deployments
if the fabrication rate exceeds a strict zero-tolerance threshold." make eval scores five
metrics and passes all five, and nothing runs it before a deployment. Add the model-path eval P2-18 asks
for and this is the same item with teeth: a threshold that stops a deploy rather than a scorecard
somebody reads. The golden set exists — 102 real filings across eight jurisdictions, all
now ingested by scripts/ingest_dockets.py.
Built 2026-08-10. app/evals/model.py is a third harness with its own entry
point, its own page and its own exit codes: 26 cases, five from the manifest and 21 drawn from seven
real version pairs, four metrics, and one of them blocks at a threshold of zero. Exit 2 means the model
was not evaluated and is never a pass. .github/workflows/ci.yml runs make test
and make eval on every push and requires the dry run to exit 2, so the nothing-sent path is
proved rather than assumed; the send path is a separate target and a dispatch-only workflow, because a
gate that bills somebody for a typo in a docstring is a gate switched off inside a week.
ADR-101. |
CLOSED 2026-08-10, and P2-18 closes with it. Three limits conceded on the page rather
than here: nobody has ever run it with --send, so every outcome path is
exercised by a deterministic fake and nothing yet says what a real model does over real filing text;
the reasoning metric rests on three labelled booleans, because two of the five manifest changes state
no materiality expectation and inventing them would be the eval writing its own answer key; and a run
in which almost every call fails still exits 0 on an empty denominator, which the page shouts and the
exit code does not. |
| I4 Flat passage lists, and a section number standing in for structure |
Verbatim: "design regulatory documents into a tree structure before diffing, rather than relying
solely on flat passage lists ... eliminating the reliance on fragile positional proxies." This names
the compromise _alignment_confidence already documents against itself: when Section 6 becomes
subsection 5.4 the words barely move, so similarity runs high exactly when structural identity has changed,
and the only defence is that a disagreeing section label caps the score at
RESTRUCTURE_CONFIDENCE_CEILING. That cap is a proxy and the docstring says so
— it turns a confident wrong answer into an honest uncertain one, which is the right trade and not a
solution. A parsed tree would let the engine align by position in the document's own hierarchy and know a
renumbering for what it is.
Measured 2026-08-11, and deliberately not wired in. Read this row before the other four. The parser exists: app/diff/structure.py reads a filing's own numbering into a tree where
it can and refuses with a reason where it cannot, and 27 of the 102 real filings yield a hierarchy. The
proxy was measured and it is as bad as the docstring feared — the leading integer fires on 42,546
of 107,122 real passages, 39.7%, returning 2025, 207 and 480 among
its labels, and 112 of 421 modified changes across the eight real pairs escalate only because a printed
gutter number shifted by one. Then the signal was wired in and measured, and it was withdrawn.
The two branches it was built for — a hierarchy contradicting two agreeing labels, a hierarchy
confirming that two passages sit under one node — fired zero times each on real
filings. All 112 movements came from a third branch nobody had authorised: the parser failing to
read both documents, and that failure being used to withhold the restructure cap, which is absence
licensing certainty. Nothing under app/ imports the parser and a test holds that.
ADR-102, docs/structure-measurement.html. |
STILL OPEN as the interview stated it, and now with a measurement under it. Alignment is still sequence alignment over a flat passage list; no tree is consulted by anything the product runs. What closed is the question of whether this parser was the answer: measured, it was not, and the negative result is the deliverable. Three things sit beneath it — the 112 false escalations are still in the queue, the named fix (the section label declining to read a leading integer on a page the gutter detector flags) moves 112 real confidences and needs its own decision, and the backward path is P2-19 below. |
| I5 The low-confidence flag reaches a screen and no decision |
Verbatim: "design a fallback routing mechanism where the deterministic engine flags low-confidence
alignments for human-in-the-loop or LLM review." Half of this exists.
app/web/views/changes.py::_shows_alignment already raises a caution on any modified passage at
or below the ceiling, and is careful not to invent doubt about added or removed passages, which carry 0.0
because nothing was aligned. What is missing is the other half: a reviewer who sees the caution
cannot act on it. There is no control to confirm the pairing, reject it, or repair it, and no
record of anyone having looked. The uncertainty is displayed and then dropped.
Built 2026-08-11. Measured first: 16 of the 27 changes in the seeded corpus carry the caution and 8 of them sit at exactly the ceiling, so the product was asking for judgement on more than half its own corpus and keeping none of it. A reviewer now answers with one of three verdicts — the pairing is right, the pairing is wrong, cannot tell — and the answer is an audit row against the change under its own action code, never a column and never a correction of the number. Two of the four states keep the caution and two lose it, decided by one predicate the screen and the chat clerk share. Answering makes the answerer an author, so they can no longer approve what follows from the change, which is the wanted outcome rather than a side effect. ADR-103. |
CLOSED 2026-08-11. The route out of the caution exists and is recorded in the chain. One piece is deliberately left open and named in the ADR: no control is drawn on a pairing the diff was confident about, and that is exactly the unlabelled renumbering the cap cannot see. A test pins the current behaviour, so opening it is a design decision rather than an oversight. Also still there: the mapping read on the same screen still scans the tenant's audit history, because its SQL ordering is free on the index the planner was already taking. |
| Gap | Detail |
|---|---|
| P3-2 | CLOSED 2026-08-10. Three stale docstrings, since corrected.
app/web/views/auth.py:31 says "no invitation flow" and
app/web/views/invite_accept.py is an invitation flow.
requirements.txt:47 says "no ORM migrations tool: the schema is created from the models
at startup" and app/state/migrate.py exists.
scripts/seed_demo_gaps.py:19 cites tests/test_demo_ready.py, which still
does not exist. The two that are fixed: projects.py no longer claims the build makes no
model call, and integrations.py is current on provenance and the fetcher. |
| P3-3 | CLOSED 2026-08-10, and the cause was not staleness. scripts/status.py ran the suite with a 120-second subprocess timeout; the suite takes 306. So every run since it outgrew that recorded "TEST COUNT NOT MEASURED" -- correctly, because a timeout really is a run with no summary, and the file refuses to invent one. The honest report was hiding a fixable cause: the instrument was too small for the thing it measures. Timeout raised to 900s, regenerated, measured=true at 2,423. It was unchanged since 2026-08-05 10:16.
tests.measured is still false, and known_gaps still lists the
_spans_of window scan fixed in ec1c4bf. This is the file two other
documents read their numbers from, so it is stale in a way that spreads. |
| P3-4 | CLOSED 2026-08-10. Retitled "A second digest scheme", with a dated note saying why. It was titled "Two digest schemes, and the old rows are never re-hashed"; there are three. ADR-53 covers the addition, but a reader scanning titles takes the count from the title. |
| P3-5 | CLOSED 2026-08-10 as a stated limit rather than a number. The two live figures in that file were re-verified and are exact: --ink-3 on --paper is 4.27:1 and --ink-2 is 7.48:1, both to the hundredth. The veil figures are teal-era, labelled as such, and cannot be recomputed from tokens -- --glass-veil is translucent and its contrast depends on the film frame behind it, which is the error verbatim.css corrected four times on one comment. The file now says so. It claimed the --glass-veil
mix reads 5.3:1. The earlier audit computed a floor of 4.98:1 and guessed a dropped digit from 5.03.
Still not re-sampled by anybody, so this stays recorded as unverified rather than
resolved in either direction. Both figures clear the 4.5:1 threshold, which is why it has stayed a
P3. |
| Was | Now |
|---|---|
| P0-1 The repository was private | Public. The decision was the owner's and it was taken. |
| P0-2 Around 13,500 lines uncommitted across four subsystems | Committed. The working tree is clean, and the scheduler, the docket fetcher, point-in-time replay and granular permissions are all in the history a reviewer reads. |
P0-3 make test failed on a fresh clone | The retention window the derived
guard wanted is declared for user_permissions in
app/state/retention.py:593. make fresh-check — which clones HEAD into a
temporary directory so nothing uncommitted can make it pass — exits zero. |
| P0-4 Every "Scope" link answered 500 | app/web/templates/review_project.html
is written, and both routes in review_centre.py render it. |
P0-5 permissions missing from nginx | In the proxy list at
deploy/nginx.conf:106, with a comment above it recording why a screen that works
locally and 404s in production is the worst shape a bug can take. |
P0-6 make run seeded zero obligations | seed_demo_gaps.py is
called by the seed target and by deploy/entrypoint.sh, last in an order the
entrypoint's comment says is load-bearing rather than tidy. |
| P1-1 "No document text goes anywhere" | deploy/site/security.html now
describes the request path as it is, and says in the same paragraph that the page used to say
otherwise. |
| P1-2 Anthropic is a sub-processor and the page denied it | subprocessors.html
names Anthropic, says what every turn of the chat panel carries, and keeps a note that the page
earlier argued the opposite at length. |
| P1-3 Safe harbour pointed at the wrong surface | The "is not deployed behind that name" sentence is gone. |
| P1-4 Six admin screens had no way in | An Admin item in the masthead,
drawn from a permission and outside the seven-screen loop on purpose — the comment in
base.html gives the reason, which is that a permission field that is always
None on every analyst row is a field the next reader has to test to disbelieve. |
P2-1 Tenant check missing in ensure_obligation | Fixed. The function
fetches through row_for_company, and its docstring carries the whole failure in capitals
so the next reader cannot remove the check by accident. This was the product's central control and
the panel would have asked. |
P2-2 _require_scope did not reject "%" | SCOPE_WILDCARDS
= ("%", "_") in app/state/queries.py, with each refused value and its reason
written out. The isolation tests were fixed in the same change: they had accepted an empty list as
proof, which is what a deleted guard also produces. |
| P2-5 The two files that protect the deploy had no tests | tests/test_config.py
and tests/test_migrate.py both exist. |
| P2-6 Coverage was not reproducible | coverage==7.15.* and
pytest-cov==7.1.* are pinned in requirements.txt, with a comment saying
they are there so the published figure can be reproduced. |
| P2-9 The demo video predated most of a day's work | Re-recorded in
a59be0c — "The film on the landing page is the film the product now is". |
| P2-10 Nobody had keyboard-tabbed the product since the glass landed | tests/test_a11y_guards.py
and tests/test_glass_contrast.py exist. The nav's unreachable items are now real
elements with the reason in the accessible name rather than in a title attribute a
screen reader may skip — WCAG 2.1.1 and 4.1.2, from one attribute. |
| P2-11 Logo mismatch | One mark. The app and the site render byte-identical SVG: the
three-bar glyph on #0d5c6b with mixed-case Verbatim beside it. That hex is
the reading taken when this row was closed on 2026-08-04 and is left as the record of it; the mark moved
to #2f4bd8 with the palette on 2026-08-07, in all six places it is painted, and the two
files are still byte-identical. |
| P2-12 The blog was not linked from the landing page | Linked twice, from the body and from the footer. |
| P3-1 The persona was called "Clerk" in one file | Clarke everywhere. |
| P3-6 Outreach counts disagreed across three documents | 45 messages, zero replies,
consistently, in the MRD and in user-research.html. The "first wave of 7" is the first
wave and not the total, which is what made the numbers look contradictory. The send log is still not
in the repository. |
site.css contrast
figure is the one left — it is recorded above as unverified rather than resolved in either
direction. Nothing on this page was carried forward from the 2026-08-04 version without being
re-run against the code first.