[Spec 250] t3code is the front end: nested Workspace > Architect > Builders, porch gates, and approval from t3code - #266
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 1 claude lane: REQUEST_CHANGES. Migration ids 900/901 would have silently shadowed every future upstream migration under Effect's watermark migrator (Migrator.js:121). Codev's columns now apply via a guarded idempotent startup ALTER outside migrationEntries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verified 042_ProjectionThreadLinkedPullRequest.ts and 7 others already use PRAGMA table_info + conditional ADD COLUMN. Our guard is that code verbatim, invoked from a layer after MigrationsLive rather than from migrationEntries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Architect ruled: Codev's columns stay out of upstream's numbered migration registry, applied from a layer after MigrationsLive with a named startup signal. Spec risk row amended under the approver's authority. Codex round: gate revision made optional to reconcile allocation with stale-rejection; codev:gate-write moved to its own RPC method since RpcAuthorization scopes methods not commands; phase 6 extends thread-backend.ts rather than adding a dead parallel module; four persistence modules named; proxy target moved to a server-held allowlist to close an SSRF path. Also corrected a false claim of my own: t3code sets no page-level CSP, so connect-src 'self' was never there to keep closed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
t3code has no Playwright; apps/web is vitest-only. Criteria 5 and 5b are browser measurements, so phases 7-9 needed a harness that did not exist where the plan put it. It now lives in this repo, which already has @playwright/test, and drives the fork's dev server. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A per-command env prefix never reaches the child porch spawns, so the lane fails and porch re-issues the same task indefinitely with every individual step reporting success. Issue #261. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eck the closure sidebarProjectGrouping.ts groups environments, not threads, so Codev's hierarchy composes with it rather than extending it. ThreadId is already in the pinned closure via baseSchemas.ts, so phase 5's closure guard will not trip on the new fields. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 'exit 0' in every report was tail's exit code, not consult's; run without the pipe the lane returns 1 and names the external_directory rejection. Porch does not spawn consult either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ective Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Third lane, additive to both others. Phase 1 now rewires acquire/start/status to upstreamBase - acquire would otherwise checkout the fork SHA into the read-only upstream clone from an ordinary test run. Phase 4 names all four RPC registration points and keeps gate commands out of the client command unions. Phase 5 vendors the method via pin.methods. Also corrects my own measurement error: the 'exit 0' reported for every failed opencode run was tail's, not consult's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three lanes, all REQUEST_CHANGES, all findings accepted - nothing in the disagree column. Plus two findings of my own: the CSP claim was false in both plan and spec, and three phases planned Playwright tests the fork cannot run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A GitHub fork inherits the source repo's visibility, so forking a public repo cannot produce the private customization this spec rules for. Phase 1 now uses gh repo create --private with upstream as a remote, asserts the created repo's visibility, keeps the MIT licence and attribution, and checks the read-only upstream clone is untouched at phase end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t refresh Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two checkouts with two meanings, built while the fork head still equals upstreamBase so every new assertion has a known answer. - pseudoseed/t3code created with `gh repo create --private`, never `gh repo fork` (a fork inherits the source repo's visibility). Branch `codev` at 082e6ea5, checked out at /Users/chris/dev/t3code-codev. Visibility asserted via `gh repo view`, not inferred. - pin.json gains upstreamBase, forkRepo, forkBranch. `commit` keeps its spec 146 meaning and becomes the fork head from phase 5. - tools/t3-fork/identities.mjs resolves the mapping once; all seven T3CODE_ROOT readers ask it rather than re-deriving a literal path. - t3-server.mjs: acquire, start and status pinned to upstreamBase, not pin.commit. acquire() writes to the read-only upstream clone and both smoke.mjs and live/integration.mjs call it, so pin.commit there would have moved that clone onto a fork sha from an ordinary test run. verify asserts both identities plus merge-base(commit, upstreamBase). - classify-churn requires --upstream-movement or --fork-drift; neither or both exits 1. Empty ranges report NO_UPSTREAM_MOVEMENT / NO_FORK_DRIFT at exit 0, distinct from 1 and from 3. - generate.mjs reads the fork; source-hash.json grows an `upstream` section at upstreamBase and a measured forkDrift block. - Cold-start evidence re-collected (t3-server.mjs changed). Port 3799 was held by another session's server, so the run used T3_HARNESS_PORT=3811. - /Users/chris/dev/t3code unchanged: branch main at 082e6ea5, clean. 47 new tests. pnpm -w test green, including spec-146-t3-contract with both live suites executing rather than skipping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eption Review nits from the claude lane. FORK.md said "nothing re-derives it" while live/integration.mjs deliberately reads T3CODE_ROOT directly and keeps it required (#214); the sentence was stronger than the code. Test section heading said "seventh readers" over a list of six. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lowed git status Both findings from the opencode/grok review. ready() called the both-identity verify one call after start() had deliberately dropped the fork requirement, so the exemption bought nothing: smoke.mjs runs acquire, verify, start, ready in sequence, and live/integration.mjs had the same shape. pin.commit does not move to the fork head until phase 5, so on phase 2's first fork commit a correct upstream server would have failed ready with CHECKOUT_MOVED_DURING_RUN for a checkout the server never touches. Adds verify-upstream and verify-fork. ready, smoke.mjs and live/integration.mjs use verify-upstream; bare verify still asserts both, which is the phase acceptance criterion. verifyCheckout caught a failed `git status` and fell through to the empty string, which is how "clean" is spelled. The comment said undetermined; the code said fine. Inherited from the spec 146 version. Now exits 3 with NO_<IDENTITY>_STATUS. The test triggers it with chmod 000 on .git/index, which leaves rev-parse HEAD working and lands the failure exactly between the two checks, and refuses to pass vacuously if the mode is ignored. Cold-start evidence re-collected after the smoke.mjs change. 52 tests in the spec 250 suite; pnpm -w test green at 7263 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rect zero-movement drill claude APPROVE/HIGH, opencode COMMENT/HIGH. Nothing blocking; four notes, all actioned. **The suite hard-asserted one of the drill's two shapes.** A `NO_UPSTREAM_MOVEMENT` run is a pass — it legitimately carries `watermark.checked: false`, `contractClosure.checked: false`, zero churn and no `preserved` block, because nothing was cloned. Three assertions would have failed on it and a fourth would have thrown. That is a test failing on a right answer, which is the mirror of the defect this phase already spent two iterations on. The shape is now named once and each branch asserts its OWN contract. The zero-movement block was verified by running the suite against a synthetic zero-movement evidence file: 6 passed, 7 skipped, where the old suite failed 3 and threw on 1. The `if (...) return` in the whole-surface test became `it.runIf` for the same reason a return is not a skip — vitest records it as a pass with zero assertions. **A comment outlived the test it described by one commit.** The drill-closure test header still said the order of the two checks was asserted below, one commit after that test was deleted for being unfalsifiable. opencode caught it. Same shape as iteration 1's finding, one file over. **The churn classification is no longer prose.** "3 source-only, 2 consumed-change-undecidable" sat outside the marker block in two documents — the rot the collector exists to stop. `classify-churn --upstream-movement` is now persisted to `codev/research/250-upstream-movement.json` and printed by the collector, with two new refusals: a classification covering a different range than the drill (exit 3 — two ranges in one table is worse than one range and a gap), and one run against the fork rather than upstream (exit 3 — the fork answering "what changed upstream" is a tautology). Deleting either guard fails its test. **"contractRegeneration in every result" was not true** of the three `could-not-run` paths. Corrected in three documents rather than padded into those paths: `could-not-run` means nothing was learned, and a measurement-shaped field on it is the first thing a reader would mistake for a finding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
APPROVE + COMMENT, nothing blocking, four notes all actioned. The sharpest is the one that inverts the phase's own defect: my tests asserted one of the drill's two shapes unconditionally, so a correct NO_UPSTREAM_MOVEMENT run would have failed three assertions and thrown on a fourth. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The defect inverted: two iterations on tests that pass when they should fail, then a test that fails when it should pass. Same missing question in both directions, and I did not think to ask it of the tests I had just written to fix asking it of the code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he rebase, not deferred to the first real one
The architect's question, and it was worth the hour. The drill proved that the
rebase MEASURES; it did not prove the contract still REGENERATES after one. Those
are different claims, and the second is the one that matters on the day a new base
is adopted — the worst possible moment to find out it does not hold.
`generate.mjs` refuses any checkout whose HEAD is not `pin.commit`, so pointed at
this repository "regenerate from the rebased tree" means moving the real pin. The
answer is not to loosen the guard. It is to satisfy it somewhere disposable:
1. `git merge-tree --write-tree` plus `commit-tree` give the merged tree an
identity inside the throwaway clone. The sequential rebase stops at commit 6,
so there is no rebased HEAD — but the generator reads only the closure, and
the closure merges clean.
2. A scratch codegen root is assembled beside it. generate.mjs resolves
pin.json, its output dir and its staging area from its own file location, so
a copy of the tool under a scratch directory reads a scratch pin naming the
merged commit and writes to a scratch generated/. The guard is satisfied
honestly, not bypassed: the artifacts really are reproducible from the commit
they name.
3. The output is compared byte for byte to what is vendored HERE — never to what
the scratch run itself just wrote, which is the tautology this file spends
three sections refusing.
Result: the contract regenerates, and `schema.json`, `schema.ts` and `types.d.ts`
all move. `ATTRIBUTION.md` and `source-hash.json` also differ and are listed
separately as `embedsCommitId`, because they name their own commit and would
differ after any rebase.
A regenerated contract that differs is a RESULT, not a failure — the same argument
as `conflicts` — so the outcome vocabulary stays three words and the artifact list
carries the finding.
The generator needs Node >= 22; the drill runs under 20. An interpreter that
cannot run it reports `attempted: false` with NO_INTERPRETER, never "the contract
does not regenerate" — that would be a claim about the fork made from a fact about
this machine. `T3_CODEGEN_NODE` overrides.
pin.json untouched, both real checkouts unmoved and clean, re-checked afterwards
as on every run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ew and the thread The architect's question closed a gap two review iterations had passed over, because every sentence that made it sound settled was true. What none of us asked was whether the constraint had a way around it that was not 'loosen the guard'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…that backs it 7396 + 180 passed, 58 skipped, 0 failed, exit 0, after the regeneration change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… was not The review file was 1,559 lines of per-phase findings and carried none of the REVIEW template's sections. It answered "what went wrong in phase N" and "did this spec get built" not at all. Adds Summary, Spec Compliance (11 criteria with phase attribution and evidence pointers), Deviations from Plan, Consultation Feedback, Lessons Learned, Architecture Updates, Lessons Learned Updates and Follow-up Items. The phase log is demoted unmodified to `## Phase-by-phase record` below them — compressing 1,500 lines of findings into four headings would lose the part worth keeping. Consultation Feedback is reconstructed from the committed artifacts: every verdict grepped out of the 44 raw lane files, every disposition from the rebuttals written at the time. Two lane files carry two verdict blocks; the last is the binding one, checked rather than assumed — taking the first would have recorded phase 11 iteration 1 as an APPROVE when it was the round that found the drill's `ok` tautology. The agy/Gemini lane produced no output for this project and is recorded as absent, not as an approval. Governance routing, with displacement: - cold, arch.md: a `### The t3code Fork (Spec 250)` section under the existing `## Integration Points` — two checkouts, two pin identities, why the upstream clone is never checked out, why the private repo is created rather than `gh repo fork`ed, what `contractSource: "fork"` changes, the schemaGuard watermark. Under an existing top-level section deliberately: a new one would have forced a thirteenth entry into the hot map, capped at 12. - hot, arch-critical.md: one fact — t3code is the front end via the private fork, the upstream clone is read-only, never `gh repo fork`, and apps/client is the FROZEN fallback. Hot because the mistake it prevents is made before anyone opens a reference doc. - displacement: the file was at 10/10, so the two-tier-governance one-liner is demoted into arch.md's Governance section, which already stated it in full. Caps after: 10 facts, 12 map topics, 32 lines. - cold, lessons-learned.md: "looks flaky, is actually under-budgeted" from phase 11, and harness runs poisoning the following suite (#263). 35 governance tests pass; the evidence collector's --check still exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… raw lane outputs actually live Every number in the review is meant to be checkable against the PR a reader is looking at. Three were not: - 105 -> 106 `[Spec 250]` commits, 128 -> 130 files. The old figures were taken against the local `main`, which was behind. `gh pr view 266 --json changedFiles` reports 130, which is what a reader sees. - The consultation total is 21 rounds (20 implementation, 1 plan), counted from the artifacts. The PR body said 22. - 43 raw lane files, not 44. And a claim that would have sent a reader looking for files that are not there: the raw `.txt` lane outputs are gitignored (`.gitignore:69`, `codev/projects/*/*.txt`). They live in the builder worktree and do not travel with the PR, so the `*-rebuttals.md` are the durable record. Said plainly rather than implied by "alongside them". Adds #267 to Follow-up Items: `consult --type pr` cannot review a PR over GitHub's 20,000-line diff cap and exits 0 when it refuses. Hit on this PR at 43,714 diff lines; both lanes refused correctly and both returned 0. Also verified rather than trusted while checking the Spec Compliance table: all four cited `spec-250-*.spec.ts` line numbers land on the test they name, and the "9 Playwright tests" counts in the evidence document are runtime counts — six static tests plus a three-viewport loop — not the static declarations a grep finds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e repository it tests
PR review round 1 — claude APPROVE, opencode COMMENT, no blocking findings.
**The phase 11 rebuttal was too broad, and being too broad hid a race.** Its
argument — the test's value is that it drives the collector against its REAL
committed inputs, so fixture copies would test a copy — is true of ONE test in a
file of six. The other five work by DAMAGING an input, and a damaged input has no
reason to be the committed one.
What that concealed: `spec-250-vendoring-identities.test.ts` reads
`codev/research/250-criterion-8b-evidence.json` in its MODULE BODY. Vitest runs
files in parallel workers, so a worker collecting it while this file held the
mutation fails on corrupted data — in a file unrelated to the collector, with
nothing in its own output to explain why. I had not looked for other readers of
the paths being mutated.
The five damage cases now run a COPY of the collector under a `mkdtempSync` root.
The collector resolves its root from `import.meta.url`, so the copy reads that
tree's inputs — the same technique `rebase-drill.mjs` uses to regenerate the
contract without moving the pin. No flag is added to the tool to suit a test,
nothing tracked is written, and a killed run leaves a temp directory instead of a
mutated repository plus a stray `.spec250-test-backup`. `spawnSync('rm', ...)`
goes with the backup file it deleted.
`agrees with the committed evidence` still runs the real collector against the
real repository. That is the assertion whose value depends on the committed
files, and it is the one the rebuttal was actually about.
**Verified capable of failing, which this needed.** Three of the five assert exit
3, and MISSING_RUN — a scratch root missing an input — is also exit 3, so an
incomplete fixture would have passed them for the wrong reason. Removing all five
mutations: 5 failed, 1 passed. Restored: 6 passed, and 83 with
spec-250-vendoring-identities.test.ts alongside it.
Also from the round:
- Two commit counts reconciled and labelled. The branch is 167 commits, 106 of
them `[Spec 250]`; both figures were stale against a local `main` behind
`origin/main`.
- #268 filed. `status.yaml` history records 9 rounds against 20 that ran, and the
pattern is exact rather than a gap: a round is recorded if and only if at least
one lane did not approve. Phases 7, 8 and 9 are absent entirely — the three
where both lanes approved on round 1. A phase reviewed cleanly reads identically
to one never reviewed. Not hand-edited.
- #267 filed before the round could run: `gh pr diff` returns HTTP 406 over
GitHub's 20,000-line cap and both lanes refused correctly, then exited 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n, and two counts that read as contradictions Three things a reader of PR #266 would have got wrong. **Cited paths cross two repositories with nothing saying so.** Criterion 8 and 11 point at `apps/server/src/codev/schemaGuard.test.ts` and `threadHierarchy.test.ts`. Neither is in this repository — they are in the fork at `pseudoseed/t3code@codev`, and a reader chasing them from the PR finds nothing. Spec Compliance now opens with the split: `packages/`, `tools/`, `codev/` are here and travel with the PR; `apps/server/` and `apps/web/` are the fork's; `apps/client` is the exception that IS here, frozen; and the Playwright `.spec.ts` files are here and run against the fork's app. **"10 of 11 criteria" over 13 checkboxes.** Both are right — the spec numbers 11 criteria and adds two sub-criteria, 5b and 8b. Said as 10 of 11 numbered, 12 of 13 counting sub-criteria, so the table and the sentence stop disagreeing. **Phase 11's "Rebutted" line contradicted the review round that overturned it.** It now says so and points at both documents: the argument held for one of the file's six tests, was applied to all six, and concealed a cross-file race. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y commit invalidates The review's summary carried an exact commit and file count. Both were corrected once this round already, and adding the round's own rebuttals moved the file count again — 130 to 131. A figure that changes when you write about it is a figure that will be wrong in the version anyone reads. The tagged-commit count stays, because it describes the work. The branch and file totals become approximate and point at `gh pr view 266 --json commits,changedFiles` for the live number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t commit `spec-1280-measurement-instrument.test.ts > T12 — determinism` failed in the full `src/__tests__/` run (1 failed, 1859 passed). I was committing review fixes while that suite ran; T12 invokes the instrument twice and compares bytes, so a commit landing between the two invocations changes what it reads. Re-run alone: 24 passed. Not flaky, not skipped — the test caught precisely what it exists to catch. Recorded with the corollary: do not commit while a suite that reads git state is running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3-way integration review
Note on how this was reviewed. The first attempt produced nothing: Two blocking, both from codex, both real1. Pre-submit network failures escape as rejected promises — on the approval path
This is the defect class this project has spent 11 phases eliminating, landing on the highest-stakes Wanted: an explicit named outcome for each pre-submit failure, and a test per outcome. 2. The proxy bounds request bodies and not responses
Not changes — my rulings, already disclosed in the body
Follow-up, do not fix hereClaude's point, and it pairs with #265: the vendored contract now sources from a private repo, and Also noted: Verified myself rather than read
|
…rk, the proxy bounds the way back Round 2 of the PR review. codex REQUEST_CHANGES with two blocking findings, after claude APPROVE and opencode COMMENT had cleared the same code. Both real, both fixed. Fork commit `2f64a1b0e`, pushed; `pin.commit` moved with it. The fork-side change and its reasoning are in that commit and in tools/t3-fork/FORK.md. What lands here is the pin, the regenerated contract, the re-exported patches, the re-run evidence, and the record. ## What the pin move actually cost, which REFRESH.md understated A fork commit invalidates every evidence run that names a fork commit — four of them — and `collect-spec-250-evidence.mjs` refuses with STALE_RUN rather than publishing numbers about a fork nobody is looking at. So criterion-8b, the hierarchy wire evidence, the churn classification and the rebase drill all had to be re-run before the evidence would regenerate. All green. That step did not exist in REFRESH.md, which said to regenerate and commit. It is the step that gets forgotten precisely because a fork commit touching no closure file changes nothing in the contract except a sha — so regeneration looks like the whole job while the acceptance document quietly goes on describing the previous fork. Now step 8, with the port guidance the re-runs needed. ## A hand-typed number, outside the block that exists to prevent hand-typed numbers The drill carries 43 commits now, not 42. "Stops at commit 6 of 42" appeared twice in prose OUTSIDE the generated block, so the collector could not reach it and it was wrong the moment the fork moved. Both now name the stop point and point at the generated row for the total. ## Regeneration Only the provenance sha moved: `schema.json`, `LOSSY.md` and `UNREPRESENTED.md` are byte-identical, because the change touches `apps/` and no closure file. `verify` clean on both identities, merge-base intact, 39 contract tests pass, and `--check` exits 0. Fork suites: web 2990 passed, server 2875 passed with only the pre-existing entrypoint symlink failure, contracts + server + web typecheck clean. ## Also - #269 filed at the architect's direction: nothing automated runs the 40 e2e tests or the contract check, and the vendored contract's source is a private repo. - #264 fired a third time — a message claiming the pr gate was approved arrived while this builder's own Playwright fixture was running. `porch status` said WAITING FOR HUMAN APPROVAL. Not acted on; evidence added to the issue. What stopped it was a note in an untracked state file, which is a habit, not a control. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ribes 32 passed at fork head 2f64a1b0ee2b. The previous run described 3786b840e1a4, and an e2e result that names a fork nobody ships is the same defect as a stale evidence number — round 1 had already flagged that staleness once. The first attempt came back 4 failed, 28 did not run, and it was self-inflicted. `T3_HARNESS_PORT=3830` is required by the evidence runs, because other sessions hold 3799 and 3823 and the harness rightly refuses to kill what it does not own. I carried it into the Playwright command, whose documented form omits it: the fixture's fork server went to 3830 while the dev server on 5733 proxied to a port with nothing behind it, so the page served 200 with no threads in it and every locator timed out against a working server. A variable that fixes one tool can break the next one in the same shell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…asurement that nearly outlived what it measured PR #266 merged as 357a364; issue #250 auto-closed. Worktree merged to the integrated state. Verification on the merged tree: - codev suite 7396 + 180 passed, 58 skipped, 0 failed, under the default node - `t3-server.mjs verify` clean on BOTH identities: upstream at 082e6ea52186, fork at 2f64a1b0ee2b on that base - `collect-spec-250-evidence.mjs --check` exits 0, so the acceptance numbers still describe the fork that shipped - patch 0034 and the moved pin confirmed present on origin/main The merge brought in one spec edit from main: the CSP correction now stands in the spec as well as the review — the same-origin guarantee is structural and a test obligation, not a header, because upstream sends no page-level CSP. Records the lesson from the merge itself. The approval said "CI is green 9 of 9"; `gh pr checks` said 9 pending. Both were true about different commits — the green run was on the last commit carrying code, and porch had since committed a status.yaml-only change that started a fresh run. Merged on green-on-the-code plus a state-file-only delta and reported it as exactly that. That is this project's own recurring defect aimed at a status report: a number carried forward past the thing it measured stops being a measurement and becomes a claim. Routed cold, with the note that it applies to reports, not only to code — a stale green in prose is as misleading as a stale number in a document and harder to catch, because nobody diffs prose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #250. Refs #146.
t3code is now the front end for Codev. Workspace → Architect → Builders renders as a tree in
t3code's own web app, porch gates render as their own thing rather than being smuggled into thread
titles, six builders tile at 1440x900 and seven tile 4x2 at 1920, and a gate can be approved from t3code over a same-origin proxy
with porch recording the session, machine and timestamp.
This PR is codev-side only. The customization itself lives in a private fork,
pseudoseed/t3code@codev, pinned at2f64a1b0ee2b. What lands here is the harness that vendors it:the pin, the regenerated contract, 33 exported patches, the tools, and the tests.
106 commits carry
[Spec 250]; the rest of the branch's ~170 are porch bookkeeping(
chore(porch): ...). 11 plan phases,builder/spir-250. GitHub's own header is the live count ofcommits and files — the figures here are the shape of the work, not a checksum of the branch.
What a human can see or do now
/codev-buildersstatus.yamlTwo blocking defects the review found, and what changed
The codex lane reviewed the finished PR after claude and opencode had approved it, and found two
things eleven rounds had not. Both are fixed here; the fork commit is
2f64a1b0e.The approval path had no answer for a dead network.
sendinapps/web/src/codev/approval.tsawaited
fetchbare, and four of its five call sites had nocatch;GateApproval.tsxhad afinallyand nocatch. A proxy disconnect while opening the session, issuing the capability,minting the nonce, or taking the synchronous fallback stopped the spinner and produced nothing —
no error, no unconfirmed state. On the approval surface that is indistinguishable from having
pressed nothing, and the builder does not advance either way.
It survived every earlier round because the file's outcome vocabulary is careful — four outcomes,
unconfirmedkept apart from refusal in five places — and nobody asked what happens whenfetchitself rejects. A rich taxonomy of answers is not the same as answering.
sendnow returns transport failure as a value,({reached:true} & Json) | {reached:false, error},so the compiler asks at all five call sites. The outcomes are deliberately different: the three
pre-submit steps report a definite
AGENT_UNREACHABLE_*because nothing was submitted and thegate provably did not move, while both submit routes report
unconfirmedbecause the requestmay have arrived and only the reply been lost. Six tests, one per outcome; restoring the throw fails
all six, by rejection.
The proxy buffered the upstream response with no limit — the same defect as the request-body
bound fixed during phase 10, on the return path, where the bytes come from configuration rather than
from an authenticated caller.
MAX_PROXIED_RESPONSE_BYTESat 1 MiB, a newoversizedoutcome thatis neither
unreachablenor a truncatedanswered, and an upstream abort. The first version of thefix settled after
destroy(), which emitserrorsynchronously — so it reported a reachable hostas unreachable until the test caught it.
Criteria 6 and 9 are unchanged: codex is right that a runbook is not acceptance evidence, and this
body says so below rather than claiming otherwise.
Acceptance
10 of 11 criteria met. Criterion 6 is UNMET and says why. Criterion 9 is met under the plan's
amended reading and not under a literal one. Full table with per-criterion evidence in
codev/resources/250-acceptance-evidence.md; the numbers in it are regenerated from the runs bytools/t3-server/collect-spec-250-evidence.mjs, not typed.Criterion 9 wants reading before you accept it. Its wording asks for a clean rebase and a
shape-checkon the rebased tree. One of those is now run and one is not.Regeneration is proved. The generator runs to completion against the rebased tree and the
regenerated contract is compared byte for byte to what is vendored here:
schema.json,schema.tsand
types.d.tsall move.generate.mjsrefuses any checkout whose HEAD is notpin.commit, sothis happens in a second throwaway —
git merge-treegives the merged tree an identity inside thescratch clone, and a scratch copy of the codegen tool reads a scratch pin naming it. The guard is
satisfied rather than bypassed, and the real
pin.jsonis neither read nor written.The rebase does not complete cleanly. It stops at commit 6, which the plan states is the
result the drill exists to produce, not a failure. The plan's phase 11 amendment settles the rest:
criterion 9 is met by the procedure completing and reporting. The evidence document sets the four
clauses against what was actually run so the reading is yours, not inferred.
Criterion 6 needs an iPad on the tailnet and no device was available. It closes unmet with an
executable 16-step runbook (
codev/resources/250-ipad-acceptance-runbook.md), not as passed and notleft open. The Playwright suite is not recorded as a substitute: it drives the same proxy in a
desktop browser, so it covers the approval path, and covers neither tailnet reach nor touch targets.
Runs behind the table:
Reviewing this
Start with
tools/t3-fork/FORK.md— every fork commit, the rebase risk table with measured besidepredicted, and a "do not tidy these" section covering two deliberate anti-rebase-debt choices.
codev/reviews/250-t3code-front-end-customization.mdcarries the retrospective — spec complianceper criterion, deviations, and the disposition of every consultation finding across 21 review rounds
(20 implementation, 1 plan) — over the per-phase findings log it was written as, which is kept below it unmodified.
The agy/Gemini lane produced no output for this project. It is recorded as absent from every
round rather than as an approval; Claude and opencode ran on every phase, plus codex on the plan.
The rebase drill (
tools/t3-fork/rebase-drill.mjs) is worth a look on its own: it measures therebase job against a live upstream 104 commits ahead, in a throwaway clone, and discards its own
result if either real checkout moved. A sequential rebase stops at commit 6, the whole conflict
surface is 3 files of the 35 we modify, 4 of the 9 contract-closure files come out of the merge with
different bytes, and the contract regenerated from that tree moves 3 shape artifacts.
Its header carries the reasoning for each of those choices, including two versions of one defect it
was itself caught in: a documented outcome no code assigned, and a measurement that would have
compared the fork to itself had it been taken one line later.
Known state, stated rather than waved at
apps/clientis kept as the fallback and frozen. Nothing from phases 7-10 is backported. Onetest fix landed (a session-status enum moved
_6→_7when the contract was regenerated in phase5), because a fallback whose suite is red is not a fallback. Gap filed as The frozen fallback client's suite runs only in CI: root
npm testfilters to @cluesmith/codev #265: rootnpm testfilters to
@cluesmith/codev, so nothing local runs it.apps/server/src/entrypoint.test.ts > matches through a symlinked entrypointfails in thefork. Pre-existing, byte-identical to the base commit, macOS
/var→/private/var. Notskipped and not modified.
proxy already gives the guarantee structurally.
agentProxy.tsratherthan closed: a total-duration bound would have to fit the slowest legitimate answer, and the
origin allowlist narrows the threat to the operator's own hosts.
porch approvesends the bare project id as theafx sendtarget and the recipient is tail-matched with leading zeros stripped, so a gate approval can reach
a same-tailed project in another workspace. Folding it in would put an unrelated change in this
PR.
Migrations
Codev's two columns stay out of upstream's numbered migration registry, via
apps/server/src/codev/schemaGuard.tsrather than a migration.FORK.mdsays why. The drillre-checked the watermark against a real new upstream migration: upstream added
043, above the042our base leaves, so it runs.🤖 Generated with Claude Code