What each deployed surface carries
rank: decision outranks: [] doc_id: PTV-DEC-0023
Section titled “rank: decision outranks: [] doc_id: PTV-DEC-0023”What each deployed surface carries
Section titled “What each deployed surface carries”Date: 2026-08-25
Status: open
Supersedes: none
Superseded-by: none — current
Prompted by: PTV-FND-0062 defect 2
Terminal state of this record: the two surfaces declared as they are, both
standing. It changes no build script’s behaviour and copies no new path.
The divergence was never decided
Section titled “The divergence was never decided”This is the most useful fact in this record and the one a future reader will otherwise supply for themselves.
Dockerfile:43 stages docs/decisions alone. scripts/vercel-build.sh:18
stages the whole docs/ tree. Neither references the other, and no record
before this one states that they differ.
They were authored independently, a day apart, by unrelated tasks:
| line | arrived in | date |
|---|---|---|
COPY docs/decisions | 77d378f3 — Task 17, petrova.host v0 on Railway | 2026-05-09 |
cp -a docs | ea745cb6 — moved out of vercel.json’s buildCommand for a 256-character limit; the copy itself predates the script | 2026-05-10 |
There is no policy to converge to. A reader who finds two conventions will assume intent and try to preserve it. There was no intent. There were two tasks.
They are not two policies. One is a policy and one is an absence.
Section titled “They are not two policies. One is a policy and one is an absence.”The Dockerfile enumerates and justifies each path in a comment beside it:
spec and schemas because otherwise “the server crashes at boot”,
contracts because it is “read at boot for the freshness log”. Every path
earns its place and says so.
vercel-build.sh copies the tree and says nothing. That is not a competing
policy; it is an unstated one. Declaring the divergence is what makes the
absence visible, and closing it is the cheap half of this record.
What each surface carries, and why
Section titled “What each surface carries, and why”Railway — Dockerfile. Enumerated, each path justified by a demonstrated
runtime need. docs/decisions is there because host/src/sources/petrova.ts:90
joins it, and that is the only disk read of docs/ anywhere in host/src.
docs/findings is deliberately absent and its absence is correct: the host
reads findings over the GitHub API, not from disk —
host/src/sources/audit.ts:3,88, App-authenticated Octokit. A finding that is
not in the image is still reachable to the surface that wants it.
Vercel — scripts/vercel-build.sh. Takes the tree. The grounded reason,
measured rather than assumed: the function bundle also embeds cli/dist
(vercel-build.sh:25), and the CLI carries disk readers for more of docs/
than the host does — docs/findings at cli/src/validate.ts:183 and
cli/src/repo-walker.ts:49, docs/decisions at
cli/src/verbs/declare_baseline.ts:71 and
cli/src/cairnet/fleet/rotation-gate.ts:22.
Not confirmed: whether any Vercel request path actually exercises those CLI readers. The bundle contains code that could read them; that is not the same as a route that does. This record states the reachable surface, not an exercised one, and marks which is which.
Note that Railway also carries cli/dist (Dockerfile:29) without
docs/findings. Any code path on that image reaching the CLI’s findings readers
would find an empty directory rather than an error. No such path is known; it is
recorded as a latent asymmetry rather than a defect, because nothing measured
shows it being reached.
The ruling: declare, do not converge
Section titled “The ruling: declare, do not converge”The two surfaces stay as they are.
Convergence would mean inventing a policy under time pressure to satisfy
G-P3-6, whose wording PTV-FND-0062 records as defective on exactly this
point — the gate does not say which console it means. Building the
reconciliation before the thing it reconciles is decided is the same error as
writing a requirements matrix before the assertions exist.
It also carries a cost that should not be absorbed silently: docs/ is 400+
files, and growing the Railway image to satisfy a clause that may not survive
the close doc’s rewording would leave the image grown for nothing. Images do not
shrink back on their own.
What happens next, and in which order
Section titled “What happens next, and in which order”- The P3 close reworks
G-P3-6’s “reachable” clause and names the surface it means, alongside theG-P3-8andG-P3-10unevaluability already listed. - If that rewording concludes both surfaces are required, convergence becomes a decided change with a stated reason — and this declaration is its prerequisite, because it is the first record of what each surface holds.
- Only then does any new path get added to either script.
Adding docs/evidence/ to the Dockerfile now would produce a third convention
rather than resolving the first two.
What this permits in the meantime: design for two, build for one
Section titled “What this permits in the meantime: design for two, build for one”A consumer that needs to read an evidence artefact from disk is not blocked by the ordering above. It is built now against the surface that already carries the path, and named for the surface that does not:
- The host source is designed for two surfaces — the path it reads is a parameter of the source, not a constant compiled into it, so adding Railway later is a registration and not a rewrite.
- It is built for one: Vercel, which stages the whole tree today and so
already reaches
docs/evidence/with no change to either build script. - The Railway source is a named follow-on, gated on step 1 above — the P3
close naming the surface
G-P3-6means. It is not deferred indefinitely and it is not silently dropped; it is blocked on a decision with an owner.
This is what “no new path is added to either script” already implies, written down because it was ruled in prose twice and did not survive to the next turn either time. A ruling that lives only in a conversation is re-litigated by the next reader of the artefacts, who has no access to the conversation.
What this does not do
Section titled “What this does not do”- It does not bless the tree-wholesale copy as correct. It records it as unstated-until-now and now stated.
- It does not rule that the surfaces must eventually match.
- It does not close
PTV-FND-0062. Defects 2 and 3 there are gate-wording problems and this record is a prerequisite for fixing them, not the fix.
Sources
Section titled “Sources”docs/findings/20260825-1900-g-p3-6-names-the-wrong-gate-and-cannot-be-read-as-written.md—PTV-FND-0062, defect 2.Dockerfile:29,41-51,scripts/vercel-build.sh:16-26— what each surface stages.77d378f3,ea745cb6— the independent origins.host/src/sources/petrova.ts:90— the only disk read ofdocs/in the host.host/src/sources/audit.ts:3,88— findings read over Octokit, not disk.CLAUDE.md— both surfaces serve a build-time snapshot.
Sign-off
Section titled “Sign-off”- Subagent: PTV-SCF-0001 P3 (session 2026-08-25)
- Human: ☑ (proxy) countersign — that the two surfaces are declared as they
stand rather than converged, that the divergence is recorded as never having
been decided, and that no new path is added to either script until the P3
close names the surface
G-P3-6means. Countersigned byhuman:devarnoon2026-08-25, by directive in session; ticked by the agent as scribe, not as signatory.