Appearance
Current state & blockers
The question this answers: what is decided, what is built, and what is waiting on a person?
This page states facts about the repository rather than rulings, and each one below was checked by reading or running what is named beside it. The rulings are in docs/adr/ and are indexed in Decisions. The state-of-now written by the last session is session-handoff.md, and it is authoritative for the blocker list at the bottom of this page.
Observed 2026-09-20. Every number here was re-derived on that date by running the command beside it. The version of this page that preceded it claimed 47 ADRs and 104 decisions against a real 56 and D-127, nine days after both had moved — which is why each row names a command rather than a source.
What exists
| Artifact | State | Checked by |
|---|---|---|
| ADRs | 75 ADRs, 6 of them superseded, every supersession recorded on both sides | ./init.sh (ADRSTATUS, IDSTATUS) |
| Decision index | Generated from docs/adr/; 75 ADRs, 148 numbered decisions, 6 superseded | bun scripts/gen-decision-index.mjs |
| Glossary | Written, and the vocabulary the ADRs and the spec both use | CONTEXT.md |
| Specification | Stage 2 written and revised, 72 US- ids | grep -ohE '^- \*\*US-[0-9]+\*\*' spec/PRD.md | sort -u | wc -l |
| Build tickets | 12 stage-3 application tickets, 13–24; every one resolved except ticket 19 | .scratch/chemical-safety-assistant/issues/ |
| Curation tickets | 19 curator-side tickets, 25–43; 25–38 and 41–42 resolved, 39 and 40 in progress, 43 open | .scratch/chemical-safety-assistant/issues/ |
| The application | Built — see the section below | ./init.sh in each app repository |
| Research notes | 112 R- findings declared across five notes in docs/knowledge/research/ | grep -rhoE '^### R-[0-9]+' docs/knowledge/research/ | sort -u | wc -l |
| This documentation site | VitePress over the repository root, ignoreDeadLinks: false | .vitepress/config.mts |
What is built
The application exists. The two entries under app/ are gitlinks — commit pointers to independent repositories, with no .gitmodules here. That layout is itself a ruling, ADR-0063, and the paths every checkout resolves against are fixed by it:
$ git ls-files -s app/
160000 acb425bc941762e3c5f2401591f80dc38d02841a 0 app/chemical-safety-assistant-api
160000 e9c9f9c4140cac9a1eba3f0e827ab741f324b6f7 0 app/chemical-safety-assistant-frontend| Surface | State |
|---|---|
| Home, manual search, QR Identification | Built |
| Chemical Record, with the chooser and the four no-answer states | Built |
| First Aid, Spill Response, Escalation | Built |
| Conversational surface, on the Routing Floor | Built, with the Escalation Trigger and Curated Explanation lookup |
| History and Settings | Built |
| Boot Splash and the First Run Notice, in front of the router | Built |
| The curator's surfaces — admission, verification, span selection, reconciliation, translation, explanation authoring, QR labels, publish | Built, in the curation build only |
| PPE as a surface of its own | A placeholder route |
| Camera text recognition | No route — blocked, see below |
| Query Intent tier | Client half built; the endpoint is deferred by ruling, see below |
Verification, run in each application repository:
| Repository | Gates | Observed 2026-09-20 |
|---|---|---|
| Frontend | LINT, TYPECHECK, TEST, SEAM4 | RESULT: PASS — 802 unit tests across 65 files, 26 Playwright cases in the Seam 4 harness |
| Api | LINT, TYPECHECK, TEST | RESULT: PASS — 777 tests across 23 files |
Both application gates were red from 2026-09-13 until 2026-09-20, in both repositories, from a single cause. Two commits written that day assumed a workspace where the two application repositories are siblings of scripts/ rather than gitlinks under app/: the api resolved corpus_check.py one directory up instead of two, and the frontend imported the snapshot golden fixture from a chem-assistant-api/ directory that has never existed on disk. Neither failure was encoded by a test, so restoring the real layout turned every one of them green. The layout is now a ruling rather than an assumption — ADR-0063.
Seams 1, 3, 4 and 5 are executable. Seam 1 is a parent-repository gate (scripts/corpus-check.sh, which runs scripts/corpus_check.py) and reports that there is no Corpus snapshot on disk to check, which is a blocker rather than a pass. Seam 2 is asserted by the frontend's surface specs. What each seam is for is in Specification, Testing Decisions.
Four golden fixtures now gate a payload shape across the two repositories rather than inside one — the Corpus snapshot, the curation refusal codes, the sixteen GHS Section headings, and the Reconciliation gaps read. Which of those shapes is a contract and which is an implementation detail is settled in ADR-0060, ADR-0062, ADR-0071 and ADR-0072.
Two capabilities the design depends on were recorded in Specification §1 as absent from the stack and therefore new work. One landed: the service worker and precached shell. The other did not: text recognition for Identification, which is blocked on a spike.
Where the work stopped
Stage 3 produced 12 application build tickets (13–24) and 19 curation tickets (25–43). What each ticket delivered is in its own file under .scratch/chemical-safety-assistant/issues/, and each carries an ## Answer naming the commit and the gate result. Four are not resolved:
| Ticket | Status on disk | Why |
|---|---|---|
| 19 — the Query Intent tier | open | Its client half is in the Routing Floor and asserted by Seam 5. The server endpoint is deferred by ruling until the Study Area's Corpus exists — ADR-0055 (D-126) |
| 39 — the selection workbench | in progress — reviewed 2026-09-20 | Built and reviewed on 2026-09-20. The review's own findings are what remain; two of them were ruled on the same day, in ADR-0062 and ADR-0060 |
| 40 — the reconciliation page | in progress — reviewed 2026-09-20 | As above. The review is what produced ADR-0067 and ADR-0068 |
| 43 — the unreadable answer in five more resources | open | New on 2026-09-20. Five curation resources still report an unparseable success body as "the service may not be running"; closing it needs Thai copy a person has reviewed |
The gate is green
./init.sh in this repository runs six checks. Its summary, observed on 2026-09-20:
COVERAGE: PASS
FIXTURE: PASS
ADRSTATUS: PASS
IDSTATUS: PASS
STACKCLAIMS: PASS
CORPUS: PASS
RESULT: PASSCOVERAGE reports 332 declared ids, all mapped downstream, with US as the frontier stage. It was red for nine days on R-46..R-112, the ids declared by the two stack-conventions research notes, which predicted that red in their own preambles; that closed on 2026-09-11. It went red again for part of 2026-09-20 while two sessions wrote in this repository at once, and closed again when the second session's own Coverage rows landed. Any COVERAGE problem now is new. Read the count from ./init.sh, not from this page.
FIXTURE PASS means scripts/test_pipeline_fixture.sh still detects a deliberately broken Coverage table, so the checker itself is working and its green is not a checker bug. ADRSTATUS and IDSTATUS PASS mean every supersession is recorded on both the superseded and the superseding document, and that no Coverage row cites a superseded finding without naming its replacement. STACKCLAIMS PASS means every package the spec claims is installed is installed — twelve of them, since the spec's claims were themselves corrected. CORPUS PASS means the gate found no snapshot on disk and so had nothing to check. It says nothing about whether anything has been curated, and until 2026-09-20 its wording did — "nothing curated yet, the Study Area is unchosen" — which was read as a statement about the project while the database held five curated documents. The Corpus lives in Postgres; a snapshot reaches that path only when one is exported to it.
Correcting a curated selection, and what the database actually holds
Two things landed late on 2026-09-20 and both change what this page could previously say.
A curated selection can now be corrected. The Corpus was append-only with no way back, and ADR-0073 had recorded that hole in its own text. Five rulings close it for selections '—ADR-0084 through ADR-0088, D-169 to D-185 '—and both halves are built: the API carries supersession, retraction, a branch act of its own and the record read that makes any of them reachable by a person, and the curator's surfaces carry all four. See Curation.
There is a reachable database, and there has been for some time. The API's own agent instructions said "None has been applied, and none has been seen by a live database" and "DATABASE_URL is empty"; both were false, and an agent repeated them twice in one session before running prisma migrate status. What is there: all eleven migrations applied, five curated documents (Acetone, Sulphuric Acid, Toluene, IPA, MEK), five Chemical Records, 79 Source Spans, six Restrictions, 83 selections and four snapshots of which one is published.
That matters for how the tests are read. They remain storeless '—every P2002 the curation write path relies on is asserted through an injected store, so the suite needs no database and runs in pre-commit '—but that is now a property of the tests rather than of the checkout. The correction path's constraints were exercised against real Postgres on the day they were written.
What the 2026-09-20 review round changed
A cross-model grill ran on ADR-0057..0063, 0067, 0068 and 0069 — two Grillers, each cold and neither a fork of the session that wrote them. Its findings are recorded in .scratch/chemical-safety-assistant/grill-findings-2026-09-20.md, and the repairs it forced are rulings rather than edits, because an Accepted ADR is never edited:
| Result | Where |
|---|---|
| Two supersessions | ADR-0073 supersedes ADR-0061; ADR-0074 supersedes ADR-0059 |
| One amending ADR | ADR-0075 amends ADR-0057, ADR-0060, ADR-0062, ADR-0068 and ADR-0069, all of which stand |
What the prototype test of 2026-09-20 changed
A Handler and a Curator used the built application and the curator's workbench, and their report was grilled against the rulings before anything was written. Eight ADRs came out of it, D-154..D-168, and three of them had to be shaped around rulings the feedback collided with:
| Result | Where |
|---|---|
| Home is six cards; an unbuilt route is absent, not disabled | ADR-0076 amends ADR-0030, which stands — D-62's peer rule binds routes that exist, and camera text returns as a card when its surface does |
| Chrome is three parts, and the alarm colour stays spent on Escalation | ADR-0077 |
| Typography and motion join the list the design extract binds | ADR-0078 amends ADR-0066, which stands |
| One control vocabulary on both builds | ADR-0079 |
| The Curator's surfaces state what the server states | ADR-0080 — the 409 that told a Curator a verified page was unverified |
| The original document is a network improvement, never a gate | ADR-0081 |
| A published version is inspectable from its own payload | ADR-0082 |
| The record tabs reference fields; emergency content stays flat | ADR-0083 — ADR-0008's rejected option is untouched |
None of it is built. These are rulings; the implementation is the next session's work.
Blockers
Taken from session-handoff.md, which is the live list. The middle column says what document governs the question; the right column says who answers it.
| # | Blocker | Governed by | Answered by |
|---|---|---|---|
| 1 | The Plant's SDS binder is not secured. Nothing can be curated without it, and with no Corpus there is nothing for the built application to show. | ADR-0011, and the abuse warning in ADR-0016 | the human |
| 2 | The Study Area is not chosen and The Plant has not supplied its chemical list (D-79, D-80). Asked 2026-09-20; the reply answered from the project poster, which names no production area, so the questions have not yet reached anyone who has been on the site. | ADR-0034, ADR-0054 | the human |
| 3 | The spend cap number is not set (D-77), and neither is the per-address bucket size, which D-103 requires to be sized for The Plant's shared egress. | ADR-0033, ADR-0043 | the human |
| 4 | Two Curation artifacts need a site authority rather than a curator — the Curated Escalation (D-95) and the Escalation Trigger phrase list (D-70). Until signed, the app ships the hotline alone. | ADR-0049 | the human |
| 5 | Camera text recognition has no route, and the spike it needs requires photographs of real drum labels. | Specification, Risks, ADR-0030 | the human |
| 6 | Corpus size is the schedule (D-58, D-59). The handoff names no answerer here; it records that if the Study Area turns out to be large, the lever is still the owner's. | ADR-0028, ADR-0034 | — |
| 7 | Nothing can correct a wrong Curated Explanation, and a soft delete locks its term out permanently. | ADR-0073 | a ruling |
| 8 | All sixteen Thai Section Headings are unsourced. GHS Annex 4 fixes the English; the Thai comes from a ministerial notification no artifact in this repository names. | ADR-0060 | /research |
| 9 | Ten Prisma migrations exist and none has ever been applied to a live Postgres. | ADR-0020 | the next session that stands one up |
Blockers 1, 2 and 6 are all the same dependency seen from three angles, and Curation is where the cost of it is worked through. Blocker 3 concerns the one networked path, drawn in System architecture. Blockers 7 and 8 are holes the 2026-09-20 rulings opened or exposed and left open on purpose; each one says in its ADR what deciding it would take.
What a reader should do next
- Reviewing the design — start at The content split, which is the decision the other ADRs bend around.
- Reviewing what was built — System architecture for the topology and the installed stack, Sources & data for what the application is fed, Cost for what it costs to run and to curate.
- Picking up the work — tickets 39, 40 and 43 are the only unfinished engineering; everything else remaining is an owner decision. Run
./init.shhere and in both application repositories first. - Looking for a specific ruling — Decisions lists all 75 with status, superseded ones included.