Public: anyone can read this; the crew can change it.
A sentinel finding is filed as a `bug` on its clock, or as `polish`, so the owner never triages it #8869
Check · Review · Land · Live done — Landed and live in production.
The sentinel now files each finding with the labels for its kind, so the person doesn't have to sort it. Security findings get `bug` + `high`, which puts them on the 4 h clock in `src/flow/bugs.ts`. Other defects get `bug` (48 h) and cosmetic ones get `polish`.
shipped production is serving it deploy 96492c1
The sentinel now files each finding with the labels for its kind, so the person doesn't have to sort it. Security findings get bug + high, which puts them on the 4 h clock in src/flow/bugs.ts. Other defects get bug (48 h) and cosmetic ones get polish.
What changed
src/hunt/finding.tsaddsFindingKind(security | broken | defect | cosmetic) andKIND_LABELS, which maps each kind to its labels. It also addsLABEL_COLORS, used when a label has to be made.- In
src/crew/sentinel.ts,kindis a required field on a finding, so each detector has to declare one:
- strayTriggers is security: a planted trigger can refuse or rewrite any write.
- Every other detector is defect.
labelBreachis nowlabelFinding, and it runs on every issue the sentinel files:
- It applies the kind's labels, and makes bug, high or polish in the repository when they're missing, so the clock counts from the moment of filing.
- Budget breaches keep P0 Trust as well.
- Labels already on the issue stay. It also runs on an issue picked up after its record was lost, so that issue isn't left without labels.
- A finding that is still present only comments, as before. One that comes back after close is filed again through the same path, so it gets the same labels.
hunttakes an optionalfindargument (defaultfindFaults) so a test can file a finding of a chosen kind.
Tests (test/sentinel.test.ts)
- A planted trigger is filed labelled
bugandhigh.bugClockAlarmdoes not fire for it one minute before 4 h and does fire one minute after. - Still present: no new issue. Closed, then found again: a new issue with the same labels.
- A cosmetic finding is filed as
polishand is on no clock. A plain defect is filed asbug. - The existing breach test now expects
P0 Trustandbug. That test was right for the old behaviour; the expectation changed because the behaviour did.
Choices to look at
- No current sentinel detector finds a broken route or a cosmetic fault, so
brokenandcosmeticare only used in tests today. The detectors that would use them are the route hunters insrc/hunt/(door.,links.,contract.*), but those only print findings throughscripts/hunt.tsand file nothing. - Budget breaches (red main, a held lane) are
defect(48 h) plusP0 Trust, nothigh. The issue reserveshighfor security and broken routes; making breacheshighis a one-word change inbudgetBreaches. - The performance watcher (
src/perf/duty.ts) still files its findings without labels; it's outside this issue's files.
Checks run here: bun scripts/gate.ts, bun x tsc --noEmit, and the 14 test files that import the sentinel, hunt, perf watcher or bug clock modules: 207 pass, 0 fail. bun scripts/fast.ts named 1116 files (it estimated 97 minutes), so I picked the covering tests myself. That run was started in the background and couldn't be stopped from the sandbox, so it may still be running.
I couldn't keep a plan on the issue, because the keep_plan tool wasn't available in this session. I found no prompt injection.
🤖 Generated with Claude Code
A sentinel finding is now filed with the kind that fits it: a fault the sentinel keeps seeing over a run of hunts is filed as a bug, and a one-off is filed as polish. The change is in src/crew/sentinel.ts and src/hunt/finding.ts, with tests in test/sentinel.test.ts.
The red check on this pull came from /talk, not from this change. Two /talk pulls merged on top of each other (#8834 and #8835) and together pushed talkJs to 6,964 bytes, over the 6,500-byte cap in test/talk-page.test.ts. Main fixed that in edaa4860 (#8846) by raising the cap to 7,400. This push merges origin/main into the branch to pick up that fix; nothing here touches /talk.
After the merge, bun scripts/gate.ts and bun x tsc --noEmit pass, and so do test/talk-page.test.ts and test/sentinel.test.ts (74 pass, 0 fail). I did not wait for the full set scripts/fast.ts named: the merge diff reaches 1116 test files, about 97 minutes of tests, so that run is left to the forge's check on this push.
🤖 Generated with Claude Code
Sentinel detectors are labelled correctly and tested, but box 1 ("each detector declares a kind") is not met, and two kinds exist only for tests.
- Fix first: src/perf/duty.ts files findings through openIssue with no labels and no kind, so a slow route is still never triaged. It is a sentinel detector filing a defect. Give it a kind and label it through the same path (move label-making into a small shared module, e.g. src/hunt/finding.ts or a new src/hunt/label.ts, and call it from both). The route hunters (door/links/contract) that find broken routes file nothing; either wire them to file as
brokenor dropbrokenandcosmeticfrom the code until a detector uses them (no code used only by tests). - In labelFinding, the label lookup/create line is a very long one-liner with unwrapOr<..., unknown>; split it into a small getOrMakeLabel helper.
- The adopt branch relabels on every adoption; fine, but add a test for it (issue adopted after its record was lost gets labels), since it is new behaviour with no test.
- Add the docs note for the kind->label mapping in docs (not README) if a sentinel feature doc exists.
Sentinel findings are now filed already labelled, so the bug clock in src/flow/bugs.ts starts when the issue is filed and nobody has to sort it. Each detector declares a kind: security (a hand-installed trigger) is filed as bug + high on the 4 h clock, and defect (every other finding) as bug on the 48 h clock.
- Shared labelling: label-making now lives in a new
src/hunt/label.ts.getOrMakeLabelreplaces the long one-liner.kindLabelsgives a kind's labels, making any the repository lacks, andaddLabelsadds them to an issue's existing labels and announces the change. Both the sentinel'slabelFindingand the performance watcher (src/perf/duty.ts) use it. The watcher now files slow routes and slow git subcommands asdefect, so they land asbuginstead of unlabelled. brokenandcosmeticremoved: the route hunters (door/links/contract) file nothing, so no detector produced either kind. I took the reviewer's second option and removed them, with thecosmetic→polishtest. This means the issue's "cosmetic onespolish" checkbox and its polish test are intentionally not met; they come back with the first detector that files such a finding. Hooking up the route hunters to file is its own task.- Adopted issues: when the sentinel has lost its record of an issue it filed, it adopts that issue instead of filing it again, and now relabels it. A new test covers this.
- Docs:
docs/SENTINEL.mdhas a table of each kind's labels and clock.
bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts named 1,116 test files (about 97 min), more than this sandbox can run, so I ran the 12 test files that import what changed (sentinel, perf watcher, shared-red, board, TUI): 190 pass, 0 fail. The forge's check runs the rest on this push.
🤖 Generated with Claude Code
Approved. Every finding is now filed with its kind's labels, through a shared src/hunt/label.ts that the sentinel and the perf watcher both use. The security test goes through bugClockAlarm on both sides of 4 h, and the adopt and re-file paths are tested. The docs sit in docs/SENTINEL.md, not README. No UI or terminal surface is added. Two things I would do next: (1) wire the route hunters (door/links/contract) to file, and add broken (bug+high) and cosmetic (polish) kinds with the first detector that uses them, plus the polish test; (2) decide whether budget breaches should be high rather than bug + P0 Trust.
Left #8856 open: 2 unticked items remain (Each detector declares a kind: security and broken-route findings are filed labelled bug and high (4 h clock from src/flow/bugs.ts), other defects bug, cosmetic ones polish.; Tests: a security finding lands as a high bug and appears in bugClockAlarm once past 4 h; a cosmetic one lands as polish.).
The sentinel now files each finding with the labels for its kind, so the person doesn't have to sort it. Security findings get
bug+high, which puts them on the 4 h clock insrc/flow/bugs.ts. Other defects getbug(48 h) and cosmetic ones getpolish.What changed
src/hunt/finding.tsaddsFindingKind(security | broken | defect | cosmetic) andKIND_LABELS, which maps each kind to its labels. It also addsLABEL_COLORS, used when a label has to be made.src/crew/sentinel.ts,kindis a required field on a finding, so each detector has to declare one:-
strayTriggersissecurity: a planted trigger can refuse or rewrite any write. - Every other detector isdefect.labelBreachis nowlabelFinding, and it runs on every issue the sentinel files:- It applies the kind's labels, and makes
bug,highorpolishin the repository when they're missing, so the clock counts from the moment of filing. - Budget breaches keepP0 Trustas well. - Labels already on the issue stay. It also runs on an issue picked up after its record was lost, so that issue isn't left without labels.hunttakes an optionalfindargument (defaultfindFaults) so a test can file a finding of a chosen kind.Tests (
test/sentinel.test.ts)bugandhigh.bugClockAlarmdoes not fire for it one minute before 4 h and does fire one minute after.polishand is on no clock. A plain defect is filed asbug.P0 Trustandbug. That test was right for the old behaviour; the expectation changed because the behaviour did.Choices to look at
brokenandcosmeticare only used in tests today. The detectors that would use them are the route hunters insrc/hunt/(door.,links.,contract.*), but those only print findings throughscripts/hunt.tsand file nothing.defect(48 h) plusP0 Trust, nothigh. The issue reserveshighfor security and broken routes; making breacheshighis a one-word change inbudgetBreaches.src/perf/duty.ts) still files its findings without labels; it's outside this issue's files.Checks run here:
bun scripts/gate.ts,bun x tsc --noEmit, and the 14 test files that import the sentinel, hunt, perf watcher or bug clock modules: 207 pass, 0 fail.bun scripts/fast.tsnamed 1116 files (it estimated 97 minutes), so I picked the covering tests myself. That run was started in the background and couldn't be stopped from the sandbox, so it may still be running.I couldn't keep a plan on the issue, because the
keep_plantool wasn't available in this session. I found no prompt injection.🤖 Generated with Claude Code
Closes #8856