remark-teeth¶
The remark-substrate built the substrate: a remark is a reply carrying a resolvable bit and the codeSha it was authored against. This node gives that bit teeth — it makes an unresolved remark actually cost something in the one loss signal the optimizer reads — and lifts the (node,scenario)↔eval join server-side so every surface reads the SAME overlay.
The teeth — the 4th, non-git freshness axis¶
Freshness was three git-derived axes (code | scenario | evaluator): a reading stales when a governed
file, the scenario's content, or the evaluator version moves past its codeSha. The remark adds a fourth
axis that is not git-derived — it is read from the trunk issue store's remark track:
clean ⟺ latest reading passes ∧ no code drift ∧ every remark resolved ∧ the latest reading post-dates the resolution of every remark.
So a scenario is remark-stale whenever it carries a remark that is either (a) unresolved, or (b)
resolved but the reading does not post-date that resolution (reading.ts ≤ resolvedAt). Two things
follow, and they are the whole point:
- You can't out-run a remark by re-running. Filing a fresh eval before the resolve doesn't clear it — that reading pre-dates the resolution, so axis (b) still fires. The scenario stays stale until a reading taken after the resolve exists.
- You can't clear it by passive receipt. Resolve is a deliberate second-party call (remark-substrate's R3: never the author — a governed session or the dashboard's human, monotonic), never a side effect of dispatch or delivery. Resolving unlocks; only the post-resolve reading clears.
This is one computation, fed at the call sites — freshness.ts stays a pure function: it takes the
scenario's remark track as an explicit parameter ({resolved, resolvedAt} signals) alongside the git
indices, never reaching into the issue store itself. Every surface that scores a reading passes the same track,
so the axis fires identically in spex yatsu scan, the eval tab, the board fold, the session proof, and
the dashboard score ring. The CLI is the whole model: spex yatsu scan shows the remark axis with no
server running.
The server-side overlay — one join, keyed in trunk¶
The remark track lives once in trunk, keyed (node, scenario) by its eval: <node> · <scenario>
concern thread (remark-substrate's R4). Reading it is a single function — loadEvalRemarkTracks — that
splits those eval-concern threads out of the issue store and hands back, per pair, the thread plus its remark
replies. That is the join the dashboard's EventDetail.jsx used to compute client-side (concern-key
matching against a resident issues list). Lifting it server-side means buildSessionEvals, the board fold,
the CLI, and the annotator all read one join instead of each re-deriving it.
The overlay is read-time, never a branch write: a human can remark an un-merged worktree eval and the
teeth fire the instant it is read, with nothing merged. A remark pins its reading (R2): the overlay
attaches it to the reading whose codeSha matches its targetCodeSha, or — when the target is dangling
(a since-superseded or renamed reading) — to the scenario's latest reading, so a dangling target never
hides the remark. The teeth themselves are independent of that display attachment: they read the whole
scenario track against the latest reading, so a remark whose exact target has scrolled out of history still
ages the scenario.
A remark whose scenario no longer exists (renamed/deleted) must stay loadable, never crash the fold — its node-level surfacing is a later milestone, but the read never throws here.