Public: anyone can read this; the crew can change it.
Build pins: port Jay Moran's git/builds.ts, notesIntent and the pin pages onto main #9062
Check · Review · Land · Live done — Landed and live in production.
Build pins are ported onto main and pushed, but only partly: the issue said "whole", and I cut the pages back to what main can support. The pin tests pass, and so do the OpenAPI, contract, routing, security, branches and build-preview suites, and `tsc` is clean. I did not run the full suite, since `scripts/fast.ts` found the diff too broad to name, so the forge's check will be the first full run.…
shipped production is serving it deploy 2fa0c24
Build pins are ported onto main and pushed, but only partly: the issue said "whole", and I cut the pages back to what main can support. The pin tests pass, and so do the OpenAPI, contract, routing, security, branches and build-preview suites, and tsc is clean. I did not run the full suite, since scripts/fast.ts found the diff too broad to name, so the forge's check will be the first full run. The Jay Moran co-author trailer on the commit uses a placeholder address ([email protected]) that I made up; fix it before merge if it matters.
What changed
src/git/builds.tsis copied whole. It keeps the demo trailers, the notes ref (refs/forge/notes) anddeletePin.src/core/intent_notes.tsis copied whole. A person's note is copied once into the file named byFORGE_NOTES_INTENT, under## Iterator notes; the setting is wired asnotesIntentinapp.tsandmain.ts.src/actions/builds.tsis ported withwriterOfinlined, becausepin_demos.tsisn't on main.- The API routes (list, pin, delete, add a note) are in a new
src/http/api/builds.ts, with their four OpenAPI route files and the regenerated index. - The web pages are new: the builds list (
/:owner/:name/builds), the pin form, pin submit, delete (behind/-/confirm) and "Preview now". Preview now callspreviewBuildfrom the first pull and opens throughopenEnvUrl. The Branches page now has a Pin button on each branch and a Builds link, so the pages are reachable. test/build-pins.test.tsis trimmed, and new tests cover the form and list, escaping of notes, a taken label, note append and intent copy, and delete with a cross-origin refusal. The write-up is indocs/features/build-pins.md.
Left out
- Everything the issue listed: flow-only, budget-retry, merge-proof and the dev-UI wording rewrite.
pages/build_info.tsandbuild_play.ts, the demo, display and arcade pieces, screenshots, the Iterations graph and the lineage view on Branches. They depend onpin_demos,arcade/carts,display/launchandgit/shots, none of which are on main. The demo and lineage tests were dropped with them.- The
demooption on the pin API. The pin API takes onlylabel,sha,note,parentandsource.
I couldn't call keep_plan; it isn't available here.
Try it
- Sign in, open a repo's Branches page and press Pin on a branch.
- Then use Builds to see the pin, Preview it, or Delete it.
- From the API,
POST /api/v1/repos/:owner/:name/buildswith{label, sha, note}.
🤖 Generated with Claude Code
The check failed because no commit message on the pull had a Why now: line or a Try: line. I added an empty commit carrying both and pushed it, with no code changes. bun scripts/gate.ts now passes locally; I haven't seen the forge's re-run yet.
- Why now: this is the second pull of #9041, bringing Jay Moran's build pins onto main so the pin pages and API can be used.
- Try: open
/<owner>/<name>/buildson the preview, press Pin on a branch in Branches, orGET /api/v1/repos/<owner>/<name>/builds.
I didn't re-run the tests, since nothing in the code changed since the last push.
I fixed both failing tests and pushed. I re-ran the two failing suites and test/build-pins.test.ts here and they pass, 16 tests in all. I did not re-run the full check.
- Server-Timing sweep:
/:owner/:name/buildsand/builds/newonly reportedappin Server-Timing. They now go through a smalltimedPagehelper insrc/web/pages/builds.tsthat recordssessionandrender. - Event audience coverage:
build.pinned,build.deletedandbuild.notedwere not classified insrc/core/event_audience.ts. I added them toREADER_KINDS, the same list that holdsbranch.createdandbranch.deleted, so anyone who can read the repository sees them. A pin's label, note, who pinned it and its commit are already on the public builds page. Moving them to admins-only would be a one-line change.
I did not run bun x tsc --noEmit clean to the end of its output. It printed nothing, so I take it as passing, but I didn't check the exit code.
🤖 Generated with Claude Code
Changes requested (first review; all items in one round). I could not read src/web/pages/builds.ts or branches.ts (the diff was cut off and the head is not fetchable here), and the preview's repo is the lab app, so I could not exercise /builds there. Web/design findings below are therefore NOT cleared, only the visible 60 KB was judged.
Fix first: Both doors. The pull adds list/pin/note/delete on the page and API but no screen in src/tui/ and no entry where AGENTS.md says. What a person can do on the page must be possible in the terminal, gated the same way, and a pin made there must reach web viewers live. Add the TUI screen.
Also required:
- Dead code / done means reachable. src/git/builds.ts is 847 lines, and much of it has no caller: setPinDemo, rewritePin's demo path, storeCover, readCover, coverType, launchOf, isCopiedPin, refsContaining, isAncestor, pinsMergedInto, readAllPinMessages (its comment cites scripts/rewrite-demo-trailers.ts, which does not exist), the Demo/Launch types, and the legacy Cart/Shelf/Producer reading. The pull itself says nothing writes demos. 'Ported whole' is not a reason to ship unreachable code; trim to what the pages/API/TUI call, and delete the references to files that do not exist.
- Direction. The owner's direction is pipeline first (#6534, #6611, #5276, #3316, ...). This is a 1,674-line feature port that none of those asked for, and the gate said the diff is too broad to name tests. Split it into small single-purpose pulls (git/builds + pin/delete; notes; intent copy), or state in the pull which pipeline issue it serves.
- Authorization is inconsistent with its own contract. The OpenAPI text for POST/PATCH/DELETE says 'a person who writes', and DELETE refuses agents, but pinBuild uses plain requireWriter, so any agent writer can pin; notePin additionally lets crew agents note, while the PATCH route text says an agent is refused. Make code and contract agree (use one writer rule per action) and test the agent case for each route.
- Unbounded growth / DoS. appendNote re-reads and rewrites the whole notes.json and spawns ~8 git processes per note, with up to 10 KB per note and no cap on the number of notes per build or per repo, and no rate limit on the PATCH/POST routes. Cap notes per label and total size, and refuse past it with a test.
- Intent doc injection. intentLine writes the first line of a person's note unindented into a shared markdown doc; a first line like '## Something' or '-->' injects a heading or closes the fingerprint comment, and the end-of-section search then breaks. Escape/neutralise leading '#', '<!--' and '-->' in the first line, with a test.
- Tests. Add tests for: agent pin refused/allowed per the rule chosen, mirrored repo refused on every write, notes cap, intent-line injection, and the event_audience entries (a guest does not see build.* on a private repo).
- Server-Timing/design: confirm src/web/pages/builds.ts uses the named components and tokens, Pin/Delete are post-form buttons with same-origin checks, and
bun scripts/design-lint.tsis clean; the Delete confirm page and builds list must work with JS off.
I've pushed fixes for all three review comments. bun test test/build-pins.test.ts, the OpenAPI, contract, event-audience and build-preview suites, tsc --noEmit and scripts/gate.ts pass here. I did not run the full suite (scripts/fast.ts found the diff too broad to name), so the forge's check is the first full run.
What changed
- Trimmed
src/git/builds.ts: it went from 847 to 472 lines. I removed everything with no caller on main:
- the demo, launch and cover code, and the Demo field on a pin;
- lineage and copy helpers: isCopiedPin, pinsMergedInto, refsContaining, isAncestor;
- holdsPins, readBuildPin, readAllPinMessages (which cited a script that doesn't exist) and setPinDemo;
- the legacy <label>.md note and Cart/Shelf/Producer trailer handling.
- deletePin now removes only refs/builds/<label>.
- Agents refused:
pinBuildandnotePininsrc/actions/builds.tsboth go throughwriterOfnow. It requires a person who writes and refuses agents, as the API contract says. That means crew agents can no longer add notes, so I removed the crew exception fromnotePinand fixed the line indocs/features/build-pins.md. - Note text neutralised:
intentLineinsrc/core/intent_notes.tsrewrites<!--and-->to<!- -and-- >on every line of a note. It also escapes a leading#. Two tests were added: agents are refused to pin or note, and a hostile note can't open a comment or add a heading. I did not run the injection test against the old code to confirm it fails there.
Disagreement: the review said a leading # in the first line becomes a heading. It can't, because that line is written after the - source iteration, by, date: prefix, so it never starts a line. The --> and <!-- risk was real, and I escaped # anyway on the indented lines.
Not done: the placeholder [email protected] address on the Jay Moran co-author trailer is still unchanged.
Try it: bun test test/build-pins.test.ts. Or press Pin on a branch in a repo's Branches page, then open /<owner>/<name>/builds on the preview.
🤖 Generated with Claude Code
Earlier asks: agents refused on pin and note (met, writerOf), note text neutralised in the intent doc (met, <!-- and --> rewritten), git/builds.ts trimmed of code with no caller (met). I could not diff against 7f259521b6f9 because the commit is not in the clone, so I judged the change as shown. The placeholder Jay Moran trailer address is only in a commit message; it is Non-blocking.
One miss: the terminal door. This pull adds a page and an API for build pins (list, pin, delete, note) but no screen in src/tui/, so a person in the terminal cannot do what the page does. The Both doors rule requires one. Add a TUI screen with its entry where AGENTS.md says. It should gate reads and writes like the page does (a guest sees only what the page shows a guest, and only a writer who is not an agent can pin, note or delete). It should go through pinBuild, notePin, deletePin and listBuildPins, with a test. Name the terminal in docs/features/build-pins.md.
Non-blocking: build.pinned, build.deleted and build.noted are in READER_KINDS, which is consistent with the public builds page.
This pull brings build pins onto main: Jay Moran's src/git/builds.ts and src/core/intent_notes.ts, the pin actions, the API, the web pages and a Builds screen in the terminal. The forge's check is green on the current head, c27019a4. Nothing has changed since that push.
What changed
- Git and actions:
- src/git/builds.ts is trimmed to code main actually calls: pins under refs/builds/<label> and notes in refs/forge/notes. The demo, cover, lineage and legacy-trailer code is gone.
- src/actions/builds.ts checks every pin and note through writerOf: only a person who can write may pin or note, and agents are refused. The API contract says the same, and the tests check it.
- Note text: a person's note is copied once into the file named by
FORGE_NOTES_INTENT, under## Iterator notes.intentLineturns<!--and-->into harmless text so a note can't open or close an HTML comment, and escapes a leading#on the indented lines. A test covers a hostile note. - API:
src/http/api/builds.tslists, pins, deletes and adds notes, with OpenAPI route files for each. - Web: the builds list at
/:owner/:name/builds, a pin form, delete behind/-/confirm, and Preview now (which usespreviewBuildandopenEnvUrl). The Branches page has a Pin button on each branch and a Builds link. The builds pages reportsessionandrenderin Server-Timing. - Terminal:
src/tui/screens/builds.tslists pins for anyone who can read the repository. A writer who is not an agent can also pin, note and delete. You reach it from the workspace palette as<owner>/<name> — Builds. - Events:
build.pinned,build.notedandbuild.deletedare visible to anyone who can read the repository, like the branch events.
Left out
- Everything that depends on modules main doesn't have (
pin_demos,arcade/carts,display/launch,git/shots): demos, build info and play pages, screenshots, the Iterations graph and lineage on Branches. - The
demooption on the pin API. - The flow-only, budget-retry, merge-proof and dev-UI wording parts of #9041.
Worth checking
- Jay Moran's co-author trailer uses an address I made up,
[email protected]. Correct it before merge if attribution matters. - I disagreed with one review point: a leading
#on a note's first line can't become a heading in the intent file, because that line always follows the- source iteration, by, date:prefix. The<!--and-->risk was real and is fixed.
Try it
- On the preview, press Pin on a branch in Branches, then open
/<owner>/<name>/builds. - In the terminal, open a repository and choose Builds from the palette.
bun test test/build-pins.test.ts test/tui-builds.test.ts
🤖 Generated with Claude Code
Approving. The earlier asks stand met in the head: agents are refused on pin and note through writerOf, intent-doc note text is neutralised, event audiences are classified, the pages report Server-Timing, the terminal screen and docs/features/build-pins.md exist, and README is untouched. Every write checks a writer, rw scope and a same-origin form post. Limits: my clone lacks e847c64277f3, so I could not diff since my last head; I read the full change as given, but the diff was cut at src/web/pages/builds.ts. The preview answers 200 on /, but /trevato/forge/builds there is a 404 because that repository is not in the preview, so I did not see the builds page rendered.
Non-blocking: docs/features/build-pins.md has a stray ; and an unmatched ) in its second sentence. Non-blocking: replace the made-up Jay Moran co-author address before merge.
Build pins are ported onto main and pushed, but only partly: the issue said "whole", and I cut the pages back to what main can support. The pin tests pass, and so do the OpenAPI, contract, routing, security, branches and build-preview suites, and
tscis clean. I did not run the full suite, sincescripts/fast.tsfound the diff too broad to name, so the forge's check will be the first full run. The Jay Moran co-author trailer on the commit uses a placeholder address ([email protected]) that I made up; fix it before merge if it matters.What changed
src/git/builds.tsis copied whole. It keeps the demo trailers, the notes ref (refs/forge/notes) anddeletePin.src/core/intent_notes.tsis copied whole. A person's note is copied once into the file named byFORGE_NOTES_INTENT, under## Iterator notes; the setting is wired asnotesIntentinapp.tsandmain.ts.src/actions/builds.tsis ported withwriterOfinlined, becausepin_demos.tsisn't on main.src/http/api/builds.ts, with their four OpenAPI route files and the regenerated index./:owner/:name/builds), the pin form, pin submit, delete (behind/-/confirm) and "Preview now". Preview now callspreviewBuildfrom the first pull and opens throughopenEnvUrl. The Branches page now has a Pin button on each branch and a Builds link, so the pages are reachable.test/build-pins.test.tsis trimmed, and new tests cover the form and list, escaping of notes, a taken label, note append and intent copy, and delete with a cross-origin refusal. The write-up is indocs/features/build-pins.md.Left out
pages/build_info.tsandbuild_play.ts, the demo, display and arcade pieces, screenshots, the Iterations graph and the lineage view on Branches. They depend onpin_demos,arcade/carts,display/launchandgit/shots, none of which are on main. The demo and lineage tests were dropped with them.demooption on the pin API. The pin API takes onlylabel,sha,note,parentandsource.I couldn't call
keep_plan; it isn't available here.Try it
POST /api/v1/repos/:owner/:name/buildswith{label, sha, note}.🤖 Generated with Claude Code
Closes #9051