Appearance
ADR-0063: The application repositories are gitlinks under app/, and a checkout reaches the workspace two levels up
Status: Accepted Date: 2026-09-20
Decisions
D-137 The two application repositories are gitlinks at app/chemical-safety-assistant-api and app/chemical-safety-assistant-frontend
From either checkout the workspace is two directories up: ../../scripts/corpus_check.py, ../../CONTEXT.md, ../../docs/adr/, ../../docs. A checkout identifies its sibling by the directory name chemical-safety-assistant-api or chemical-safety-assistant-frontend.
Context
This was never decided, so a commit assumed it instead. The api commit 5a93e9a (2026-09-13) changed the checker path from two levels up to one and rewrote the api's AGENTS.md and README.md to cite ../CONTEXT.md, ../docs/decisions/ and ../docs. All three are absent — the workspace has docs/adr/, not docs/decisions/ — so the api's own instruction file points every agent that reads it at paths that do not resolve.
Both gates are red because of that assumption. Measured 2026-09-20:
- API
RESULT: FAIL.LINT PASS,TYPECHECK PASS,TEST FAIL: 10 failures of 712, every one fromcorpusCheckPath()resolvingapp/scripts/corpus_check.py, which does not exist. No test encodes the bug —publish-snapshot.spec.ts:158asserts only that the checker is reachable — so restoring two-level resolution turns all ten green. - Frontend
RESULT: FAIL.LINT PASS,SEAM4 PASS,TYPECHECK FAIL,TEST FAIL:src/tests/seams/SnapshotSeam.spec.ts:18imports../../../../chem-assistant-api/…/snapshot.golden.json. Commitcec894d(2026-09-13) introduced it with a comment asserting the api's directory ischem-assistant-api. It ischemical-safety-assistant-api.
The layout on disk is not in doubt: .gitignore carries app/*/ entries, git ls-files -s app/ records both gitlinks, and no checkout in the other shape has ever existed anywhere under the projects directory. What was missing was a ruling, so that the fix is a ruling being applied rather than one person's guess overwriting another's.
This is load-bearing rather than clerical because the path in question is the publisher's. The gate that runs at publish and nowhere else (D-127) is corpus_check.py, and it is what enforces the reconciliation rule (D-44) over a candidate snapshot. A publisher that cannot reach the checker is a publisher that cannot run the gate.
Decision
The application repositories are gitlinks under app/, named in full. Every path from a checkout to the workspace is two levels up, and a checkout finds its sibling by the full directory name.
Rejected options
- Relocating the two repositories to sit beside
scripts/aschem-assistant-apiandchem-assistant-frontend— rejected. It is the layout the 2026-09-13 commits assumed, no checkout in that shape has ever existed on disk, and adopting it would move both gitlinks and touch.gitignore, the VitePresssrcExcludeand every document namingapp/. Making the documents true by moving the repositories they describe is the expensive direction. - Leaving each repository to resolve the workspace however it does, and fixing the failures where they appear — rejected because that is what produced two red gates from one assumption. Ten API tests and a frontend import failed for the same unstated reason, and neither failure named it.
- Encoding the path in an environment variable each repository reads — rejected because it moves the assumption from a file into a machine, where the next checkout has no way to discover it and no gate can fail on it being wrong.
This ruling may not be re-decided
If a change contradicts this ADR: stop and raise it. Do not implement over it.
Specifically: do not move either repository out of app/, do not shorten either directory name, do not resolve the workspace one level up from a checkout, and do not cite docs/decisions/ — the decisions are in docs/adr/.
Consequences
What becomes true. Both application gates can go green against a layout that is written down. The publisher can reach corpus_check.py, so the publish gate runs and D-44 is enforced over a candidate snapshot rather than skipped. An agent reading either repository's AGENTS.md is sent to paths that exist.
What this costs. Two levels of .. in every cross-repository path, which is what a gitlink layout costs and what the 2026-09-13 commits were trying to avoid. Each repository has to be corrected — the api's checker path and its two documents, the frontend's seam import — and this ADR is the authority for those edits, which belong to the repositories' own agents.
What is closed off. The flat layout beside scripts/, and any per-machine configuration of where the workspace is.
Coverage
| Upstream | Landed in | Evidence | Note |
|---|---|---|---|
| D-127 | D-137 | the publish gate is corpus_check.py run by the publisher, so the resolved path is what decides whether the gate runs at all | ADR-0056; the 10 API failures are all corpusCheckPath() |
| D-44 | D-137 | the reconciliation check the publish gate enforces is unreachable when the checker path is, which is why the layout is a ruling and not a preference | ADR-0022 |
| D-124 | D-137 | the frontend's snapshot seam imports the api's golden fixture by directory name, so the sibling's full name is part of the cross-repository check | ADR-0053; SnapshotSeam.spec.ts:18 |