Bench v1 — the morning projection

Your world, on your phone, with your coffee: five cards max, each in the words a human wrote when the thing settled, each with its receipt one tap away. Anything that needs you is fenced and first; if nothing does, the page says so. You mark ✓/✗/? and tomorrow's cards reflect it. Ninety seconds, two-minute hard ceiling. This surface reviews the spec — the walk script you already shaped is canonical and wins on any conflict.

One call is open for you (starred, bottom). Everything else here is decided, two independent review rounds deep — red-pen anything, but nothing blocks on you except that one.

Reference walk: where cards come from · verdict classes · mark identity · ruling reflection · deploy & rollback · your one call · what could go wrong

The morning, beat by beat — and what each beat decided

  1. He opens it: "is this NOW?" One header line — "Your world as of 7:23 this morning" — with the pin, small. If the driver died, a loud banner replaces the header instead: "rendered yesterday 15:23 — treat everything below as dated." The banner math respects your real mornings: the overnight gap between driver runs is sixteen hours, so the page only cries stale when a render is genuinely overdue (past its own next-expected time plus 45 minutes) — a review round caught that the naive threshold would have false-alarmed at your 7:00 coffee every single day.
    ↘ go deeper — staleness mechanics
    Each render embeds next_expected_render_at computed from the driver cadence (7:23/15:23 local). Client-side check at open time: Date.now() > next_expected + 45min → banner replaces header. Both elements exist in static HTML; the property is enforced at open time, so gate G-B5 runs in an executed-DOM harness under a mocked clock — raw-bytes assertions would pass while the property is false. Fixtures: dead-driver → banner, no header; healthy 16h overnight gap → header, no banner. First render (no prior receipt): window = trailing 24h, "first render" in the header, meter card suppressed.
  2. The cards: five, max — only words humans already wrote. Each card is one thread's movement since you last sat down: a one-line story, its verdict class, its currency. The line is the recorded settlement text — a deposit's claim, a PR title — never fresh prose minted at render time. No card without its receipt one tap below. A sixth thing that moved is one quiet line, never a sixth card. And the bench never eats itself: its own records (render receipts, your echoed marks) can never become cards — a review round caught that without this fence the surface self-poisons by day two while passing every gate.
    ↘ go deeper — sources, clustering, ranking
    Sources (all existing): fact-plane deposits (origin: bench:* excluded from candidacy — gate G-B2b plants one and it must not render) · intent-plane calls/leases · trunk merge subjects · LAW-2 meter state (a card only on state change). Cluster key: PR number when named, else normalized subject — never fuzzy. One-liner: most recent settlement text in the cluster. Ranking: needs-him first, then movement size, then recency; cap 5 is a runtime constructor cap with G-B1's force-a-sixth mutation. Naked claims (no receipt+currency) never render and are counted in the receipt's dropped_naked. Selection is a pure function: same inputs → byte-identical page (G-B6, plus a scope-aware ban on free fetch/network references in src/bench/ — import-reach alone can't see Node's global fetch).
  3. What needs him: fenced, first, or explicitly released. Cards carrying your call wear it visibly and sort first — always. If more than five need you, the overflow line says "1 more needs you — ask me", never "moved quietly." And whenever nothing needs you — even on a morning full of movement — the page says "Nothing needs you today." in words. The walk never ends in ambiguity about whether you're done.
    ↘ go deeper — verdict classes, mechanically decided
    Exactly one class per card, by precedence: waiting-on-you (open intent-plane call) > stale (intent record past its lease/TTL horizon, unrenewed) > shipped (trunk merge) > moved (other fresh activity). blocked is not emitted in v1 — no mechanical predicate exists, and a class is never guessed from prose. Release line renders whenever the needs-him set is empty, independent of movement (G-B4's movement-only fixture: 4 cards, 0 calls → release line shows). Page currency lives exactly once, in the header; each card's claim-pin exactly once, on its card (G-B3).
  4. He marks: ✓ approve · ✗ kill · ? question. On any card, plus a free-note box even on the empty page. A mark shows it took — honest about being pending until the driver's next pass. Writing a forged mark requires the same secret as reading the page: the page's unguessable token is folded into the sync channel itself, and the driver ignores everything else. Two devices marking in the same minute is the one narrow window v1 accepts and names.
    ↘ go deeper — mark identity & idempotency
    POST to the existing /__sync KV endpoint with specId: bench-marks-<token>; client-minted mark ids (hash of claim-id + mark + ts); merge-before-POST plus localStorage self-carry per device (last-write-wins substrate, so the client carries the merge — G-B8's merge-carry mutation: a stale-tab POST without merge must make a prior mark vanish → red). Driver dedupe anchors on existing bench:mark deposits by id — the same plane it writes to — because anchoring on receipts is circular in exactly the receipt-failure branch (G-B8's receipt-missing variant: deposits exist, receipt absent → second pass still mints zero). Every mark deposit is labeled origin: bench:mark (unauthenticated channel) — provenance never dressed up.
  5. The loop closes: tomorrow's cards reflect his rulings. Your mark becomes a deposit on the driver's next pass. A card you ruled on comes back demoted and says so — "ruled ✗ yesterday — recorded" — never re-fenced as if you'd said nothing. But a mark is testimony, never an auto-verdict: an unauthenticated channel must not silently close a canonical call, so the formal settlement of the underlying intent stays with the normal machinery. The bench neither eats itself nor fails to digest you — both directions were caught in review and both now carry gates.
    ↘ go deeper — demotion mechanics
    A cluster whose open call carries a recorded ruling (a bench:mark deposit or receipt marks_seen entry matching its cluster key) leaves the fenced set, ranks with movement, carries the annotation line with relative-day, and counts as answered for the release line. G-B9's fixture: call-marked-yesterday → demoted, annotated, NOT re-fenced, release line shows; mutation: re-fence the ruled call → red. Residual (benign, named): a ruled-but-unsettled call keeps rendering demoted until the lead formally settles it via intent machinery.
  6. How it ships — and how it dies. The bench never deploys on its own: the site is one shared snapshot, rebuilt from committed truth every driver cycle and published in ONE deploy — review caught that both an independent bench deploy AND the current scratch staging dir could gut the live site (roadmap, review surfaces, the comment-sync endpoint) on a fresh machine, so the deploy now refuses to publish an incomplete snapshot. Any render failure means no deploy that cycle: a stale-but-consistent site plus the loud banner beats a silently mixed one. Rollback is one commit — the bench lifts out of the snapshot without touching anything else. The judge was named before this spec existed: it survives one real morning of your attention, or we iterate on the walk — and if two iterations don't fix it, we park it and say so.
    ↘ go deeper — staging manifest, gates, DoD
    Deliverable 4 commits functions/__sync.js + wrangler.jsonc (today they exist ONLY in gitignored scratch) and adds staging.json (committed source → snapshot path) + stage-snapshot.mjs rebuilding .roadmap-build/ fresh each cycle; G-B11 fails the deploy closed if any manifest entry is missing (mutation: delete __sync.js from staging → deploy must refuse). Twelve gate legs total, [M]/[J] courts declared, every [M] leg carrying a mutation that must go red; final [J] leg is the walk-audit on your actual phone viewport before you ever see it. DoD: gate green · manifest live · bench at the token path after one driver cycle · receipt deposited · driver duty patched · walk-audit logged. The staging manifest survives a bench rollback on purpose — it fixes a defect that predates the bench.

Decided (red-pen if any feels wrong)

Cards render ONLY recorded-at-settlement text — no fresh prose, ever; render is deterministic and gated byte-identical
"Workstream" v1 = explicit cluster key (PR number, else subject) — never fuzzy-matched; the fix for fragmentation is better subjects, not renderer guessing
A mark is testimony, never auto-verdict — it demotes tomorrow's card; settling the intent record stays a deliberate act through normal machinery
"Since you last looked" = since the previous render receipt; marks-prove-reading comes later when the bench is an organic consumer

What could go wrong (honest)

Ranking lies by omission

A needs-you item misses the top five. Fence: needs-you always sorts first, the overflow line names it in those words, and the render receipt accounts for everything not shown.

The driver dies quietly

The stale banner is a gate, not a hope — fixture-tested both ways under a mocked clock. The deeper fix (a driver that survives sessions) is its own named build, fenced visibly, not hidden here.

The read exceeds two minutes

The ratified rider governs: iterate on the walk, not the plumbing; two failed iterations → park it and say so.

Your call

★ Marks channel auth posture — default taken: unauthenticated v1

The marks channel ships unauthenticated behind the page's secret (same posture as this very review surface), every mark labeled as coming from that channel. If you want the bench behind Cloudflare Access instead — a login wall on your morning bookmark — say the word; it's a config flip, not a rebuild. Ship proceeds on the default; nothing else needs you until the walk-audit puts the page on your phone.