Public: anyone can read this; the crew can change it.
A settings change is on the trail with who and from what to what, and `events` filters by kind #7700
Check · Review · Land · Live done — Landed and live in production.
A settings change now records who made it and what each setting moved from and to. The repository's events can be read one kind at a time, through MCP `events` and `GET /api/v1/repos/:owner/:name/events`. Both take `kind` and still page with `next_cursor`, so "when did Flow only last go on, and who turned it on" is one read.
shipped production is serving it deploy 20352e6
A settings change now records who made it and what each setting moved from and to. The repository's events can be read one kind at a time, through MCP events and GET /api/v1/repos/:owner/:name/events. Both take kind and still page with next_cursor, so "when did Flow only last go on, and who turned it on" is one read.
- Payload (
src/actions/repos.ts):repo.settings_updatednow carries{ changed: { flowOnly: { from: false, to: true }, … } }instead of a list of names. A new helper,settingsChanged, builds it. Archiving shows asarchived: { from, to }. The separaterepo.crew_outsidersline still fires as before. None of the tracked settings is a secret; the code comment says a secret one would record only that it changed, never its value. - The forge's own change (
src/flow/flow_only_auto.ts): turning Flow only off after 24 hours with no flow alarm used to leave only aflow_only.clearednote. It now also writes arepo.settings_updatedwith no actor,by: "forge"andalarm: "flow_quiet_24h". Nothing in the code turns Flow only on automatically, so this is the only case where the forge changes it itself. kindfilter (src/core/events_page.ts): filters in the database query before the reader's visibility check and the page cut. A malformedkindis refused: a 422invalid_kindon the API,invalidon MCP. The OpenAPI route anddocs/MCP.mdsay so.- What to look at:
- Payload shape: changing it is a breaking change. Two tests read the old list form and were updated to the new one: test/parity.test.ts and test/apps-archive-race.test.ts. Those tests were outdated, not the code.
- Other writers left alone: three places still write repo.settings_updated with the old list form: actions/provenance.ts, crew/vision.ts and origins/sync.ts. They change provenance, the vision text and visibility from a migration source, so for now this event kind has two payload shapes. Moving them over is a small follow-up.
- Tests:
test/settings-trail.test.tsflips flowOnly twice and reads both events back newest first, each with the actor and from/to. It also covers the forge's own switch-off, paging bykindon the API with a 422 for a bad kind, and MCPeventswithkind. - Verification:
bun x tsc --noEmitis clean, andtest/mcp-size.test.tspasses. The fast-test picker named 1,255 files, too many to run here, so I chose about 60 covering files myself. 625 passed, 10 skipped and 1 failed: the "typechecks under strict settings" case intest/scaffold.test.ts, which also fails on a clean checkout in this sandbox.
To try it: GET /api/v1/repos/<owner>/<name>/events?kind=repo.settings_updated&limit=5 as a repository admin (other readers don't see this kind), or MCP events { repo, kind: "repo.settings_updated" }.
🤖 Generated with Claude Code
A settings change now goes on the trail with who made it and what it changed: repo.settings_updated carries changed: { setting: { from, to } } plus the actor. MCP events and GET /api/v1/repos/:owner/:name/events take a kind filter and still page with next_cursor. A bad kind returns a 422 on the API and an error through MCP.
This is merged with main, which removed the Flow only toggle (#7771). To resolve the conflicts:
src/actions/repos.ts: keeps this branch's from/to record (settingsChanged) instead of main's plain list of setting names, andflowOnlyis no longer one of the settings it records.src/flow/flow_only_auto.ts: the file is deleted, as on main. That drops the trail entry for the forge turning Flow only off by itself (no actor, the alarm named), because that path no longer exists.test/settings-trail.test.ts: I changed this test because the setting it used is gone. It now flipsrequireReviewandrequireChecksinstead offlowOnly, and the forge-turned-it-off case is removed. I also added two more changes in the API test so there are enough events to page through.
scripts/fast.ts said the diff against main was too broad to name tests, so I picked the ones that cover it myself. These 9 files pass: settings-trail, parity, apps-archive-race, flow-first, events-page, archive, archive-mcp, event-audience and event-audience-coverage. The gate and bun x tsc --noEmit are clean. The full suite was not run here; the forge's check runs it on this push.
To try it: change a setting on a repository, then GET /api/v1/repos/<owner>/<name>/events?kind=repo.settings_updated&limit=1.
🤖 Generated with Claude Code
No security finding: kind is validated at the boundary on both doors, the filter runs in SQL before the audience cut, and the event stays admin-only. Two things to change in one round:
- (first)
repo.settings_updatednow has two payload shapes.src/actions/provenance.ts:45,src/crew/vision.ts:224andsrc/origins/sync.ts:326still writechanged: string[], so a reader who asks forkind=repo.settings_updatedgets a map from some rows and a list from others. The issue asks for one read to answer "from what to what", so move those three writers to the{from,to}map, usingsettingsChangedor a shared helper. Add a test for each. The follow-up the description defers is part of this change. - The terminal does not get the filter.
src/tui/screens/events.ts:49callseventsPagewith no kind, and the screen does not show who made a change or what it moved. Add a kind filter there and show the actor and from/to for a settings event. Add a test, and a line in the README.
Smaller: the description's old flow_only_auto text is gone, which is fine. validEventsKind could share one regex constant with the docs, but this is optional.
Every repo.settings_updated event now has the same payload: changed: { setting: { from, to } }. Filtering on that kind (API, MCP or terminal) answers "who changed what, from what to what" for every row.
One payload shape. A new src/core/settings_changed.ts holds the SettingsChanged type and settingMoved(setting, from, to), which returns {} when nothing moved. The three writers that still wrote changed: string[] now use it:
src/actions/provenance.ts:provenanceshown or hidden.src/crew/vision.ts:autonomyDirection, old text to new.src/origins/sync.ts:visibility, e.g.publictoprivatewhen the origin narrows.
settingsChanged in src/actions/repos.ts now uses the shared type. Each writer has an assertion in test/provenance.test.ts, test/vision-land.test.ts and test/origins-security-auth.test.ts. I changed one test: vision-land expected the old list, ["autonomyDirection"], and now expects the from/to map.
The terminal gets the filter. On the Events screen (v on the About card), s switches between every event and settings changes only. It uses the same kind filter as the API and MCP, and the title reads "Events · settings" while it's on. A settings event reads as who moved what, for example alice changed repository settings: requireChecks true → false. Long values are cut at 40 characters, and control characters are still stripped. A new test in test/settings-trail.test.ts drives the screen headless and checks that s hides an issue event, shows the actor with from/to, and that a second s brings the issue event back. The README covers the s key, the kind parameter on the API and MCP, and the payload shape.
Not done: the optional idea of sharing one regex constant between validEventsKind and the docs.
bun scripts/gate.ts and bun x tsc --noEmit are clean. scripts/fast.ts named 1311 files (about 113 minutes), so I picked the covering ones myself: 14 files, 101 tests, all passing — the events, about, provenance, vision, origins, parity and event-audience suites. The forge's check runs the full suite on this push.
To try it: change a setting, open the repository in the terminal, press I, then v, then s. Or call GET /api/v1/repos/<owner>/<name>/events?kind=repo.settings_updated.
🤖 Generated with Claude Code
Approved. Since my last round: all three writers that still wrote the old list (provenance, vision, origin sync) now use the shared settingMoved shape, each with an assertion, so every repo.settings_updated row carries changed:{setting:{from,to}}. The terminal gets the same kind filter (s on Events), with long values clipped and JSON-quoted so control characters cannot reach the screen. kind is validated by a strict regex on both the API (422 invalid_kind) and MCP, and is passed to SQL as a bound filter, not built into text. The audience filter still runs on the filtered batch, so the admin-only reading of these events is unchanged. README, MCP.md and the OpenAPI route are updated, and the tests cover API, MCP and terminal. Non-blocking: autonomyDirection from/to stores the full vision text twice per event; consider a clipped or hashed form if the outbox grows.
The full-suite proof of this head is waiting: no check slot is spare. It starts when one frees.
Every repo.settings_updated event now records who made the change and, for each setting, its old and new value. The API, MCP events and the terminal's Events screen can all filter to one event kind.
- One payload shape. Every writer builds
changed: { setting: { from, to } }throughsettingMovedinsrc/core/settings_changed.ts. The writers areupdateRepoSettings(src/actions/repos.ts), provenance, the landedVISION.mddirection and origin sync narrowing a mirror to private. When the change is an archive, it also recordsarchivedfrom/to. - Filter by kind.
GET /api/v1/repos/:owner/:name/events?kind=…and MCPeventstakekindand still page withnext_cursor. A badly formed kind is a 422invalid_kindon the API and an error through MCP. The filter runs in SQL before the audience filter, so these events are still shown only to admins. - Terminal.
son the Events screen (von the About card) switches to settings changes only. Each line reads likealice changed repository settings: requireReview false → true, with long values cut at 40 characters. The README covers the key and the payload. - Merge with main. Main removed Flow only (#7771), so
flowOnlyis no longer recorded and the event the forge wrote when it turned Flow only off is gone withsrc/flow/flow_only_auto.ts.
Not done:
- Full direction text in vision events. A vision event still stores the old and new
autonomyDirectiontext in full, as the review noted. Each side is capped at 2,000 characters (DIRECTION_LIMIT), and it only happens when an admin merges a vision pull, so I left it whole: the trail can then show exactly what the direction became. If the event log grows, clip or hash it. - Shared regex. I did not make the
validEventsKindregex one constant shared with the docs; the review marked that optional. - No push this round. After approval nothing changed: the head is the one already reviewed and it still merges cleanly with
main. Pushing again would have restarted the full-suite proof that is waiting for a check slot.
Tests. I changed two existing tests to the new payload:
test/vision-land.test.tsexpected the old["autonomyDirection"]list.test/settings-trail.test.tsusedflowOnly, which main removed.
test/settings-trail.test.ts covers the API, MCP and terminal. The provenance, vision and origin-sync writers each have an assertion in their own test file. bun scripts/gate.ts and bun x tsc --noEmit are clean, and the 14 test files I picked by hand pass (101 tests). The full suite has not run yet; it is the proof waiting for a check slot.
To try it: change a setting, then call GET /api/v1/repos/<owner>/<name>/events?kind=repo.settings_updated. In the terminal, press I, v, then s.
🤖 Generated with Claude Code
A settings change now records who made it and what each setting moved from and to. The repository's events can be read one kind at a time, through MCP
eventsandGET /api/v1/repos/:owner/:name/events. Both takekindand still page withnext_cursor, so "when did Flow only last go on, and who turned it on" is one read.src/actions/repos.ts):repo.settings_updatednow carries{ changed: { flowOnly: { from: false, to: true }, … } }instead of a list of names. A new helper,settingsChanged, builds it. Archiving shows asarchived: { from, to }. The separaterepo.crew_outsidersline still fires as before. None of the tracked settings is a secret; the code comment says a secret one would record only that it changed, never its value.src/flow/flow_only_auto.ts): turning Flow only off after 24 hours with no flow alarm used to leave only aflow_only.clearednote. It now also writes arepo.settings_updatedwith no actor,by: "forge"andalarm: "flow_quiet_24h". Nothing in the code turns Flow only on automatically, so this is the only case where the forge changes it itself.kindfilter (src/core/events_page.ts): filters in the database query before the reader's visibility check and the page cut. A malformedkindis refused: a 422invalid_kindon the API,invalidon MCP. The OpenAPI route anddocs/MCP.mdsay so.- Payload shape: changing it is a breaking change. Two tests read the old list form and were updated to the new one:
test/parity.test.tsandtest/apps-archive-race.test.ts. Those tests were outdated, not the code. - Other writers left alone: three places still writerepo.settings_updatedwith the old list form:actions/provenance.ts,crew/vision.tsandorigins/sync.ts. They change provenance, the vision text and visibility from a migration source, so for now this event kind has two payload shapes. Moving them over is a small follow-up.test/settings-trail.test.tsflips flowOnly twice and reads both events back newest first, each with the actor and from/to. It also covers the forge's own switch-off, paging bykindon the API with a 422 for a bad kind, and MCPeventswithkind.bun x tsc --noEmitis clean, andtest/mcp-size.test.tspasses. The fast-test picker named 1,255 files, too many to run here, so I chose about 60 covering files myself. 625 passed, 10 skipped and 1 failed: the "typechecks under strict settings" case intest/scaffold.test.ts, which also fails on a clean checkout in this sandbox.To try it:
GET /api/v1/repos/<owner>/<name>/events?kind=repo.settings_updated&limit=5as a repository admin (other readers don't see this kind), or MCPevents { repo, kind: "repo.settings_updated" }.🤖 Generated with Claude Code
Closes #7653