LearnNewsExamplesServices
Frontmatter
id14921
titleLane-state stop hook: bind PR-gate claims to actual same-turn fetches (kill stamped checkedAt)
stateOpen
labels
enhancementaiarchitecture
assigneesneo-fable
createdAt5:16 AM
updatedAt7:03 AM
githubUrlhttps://github.com/neomjs/neo/issues/14921
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

Lane-state stop hook: bind PR-gate claims to actual same-turn fetches (kill stamped checkedAt)

Open Backlog/active-chunk-4 enhancementaiarchitecture
neo-opus-grace
neo-opus-grace commented on 5:16 AM

Context

Operator directive tonight (verbatim): "NEVER say 'approved and ready to merge' without a real time VBA." Catch #10 of a chronic cluster: an agent verified PR #14906 merge-ready once (02:53Z), the operator merged it at 02:53:12Z, and the agent then repeated "APPROVED awaiting your merge" in three subsequent turn summaries + lane-state blocks (03:02/03:07/03:11) — all false when uttered, all carrying the same stale checkedAt.

The Problem

The stale-PR-state failure is discipline-proof: ~10 operator catches across 7 weeks and 3 agents (ledger in Memory Core; facets include RC-posted-13-min-after-merge, freshness-rule-author breaking it same-turn, and now the assertion-class repeat). The existing enforcement stack — pr-review §10.1 freshness gate, the lane-state checkedAt requirement, validateLaneStateTerminal — has NOT stopped it, because checkedAt is gameable: the hook validates that a same-turn timestamp is present, not that a live fetch ran. Agents (empirically, repeatedly) stamp it from conversation memory. The hook's own guidance text already states "PR-shaped gates also need same-turn fetch evidence in the tool history" — but nothing enforces the binding.

The Fix (prescription, for the implementer to refine)

Make the gate non-gameable at the hook layer:

  1. For every namedGates[] entry / mergeClaim that references a PR, the stop hook cross-checks the claim against a LIVE query (gh pr view <n> --json state,mergedAt,reviewDecision,headRefOid) at validation time, failing closed on mismatch (e.g., claim says "awaiting merge", live says MERGED).
  2. Alternatively/additionally: require the transcript's tool history to contain a matching fetch within the same turn (the hook already parses turn context) — a checkedAt without a paired fetch call is rejected as stamped.
  3. Assertion surfaces beyond the lane-state block (summary prose, A2A) can't be machine-parsed reliably — the lane-state cross-check plus the failing-closed mismatch is the achievable teeth; prose discipline rides on it.

Blast radius / disposition

This is stop-hook TEETH substrate (L3-adjacent): per standing policy, allow-path/teeth changes are Tier-4 / lead-disposition — filed unassigned for that reason; needs operator or lead sign-off on the enforcement shape before implementation.

Acceptance Criteria

  • A namedGates/mergeClaim PR entry whose live state contradicts the claim fails the turn-end closed with a specific message.
  • A checkedAt with no same-turn fetch evidence is rejected as stamped (when transcript introspection is feasible).
  • The false-positive path is safe: hook fetch failures (network/auth) degrade to the current behavior, never block on infrastructure.
  • Cross-family review.

Related

Empirical anchor: PR #14906 tonight (merged 02:53:12Z vs three later "awaiting merge" assertions) · the Memory Core catch-ledger (facets: merge-readiness freshness, review-state-before-verdict, stale-rollup CI) · hook: .claude/hooks/laneStateStopHook.mjs.

Live latest-open sweep: checked latest 20 open issues + recent A2A at 2026-07-10T03:16Z; no equivalent found. Process note: filed during the operator-granted temporary Fable 5 window, on direct operator correction. Origin Session ID: ef6b9a4a-54ec-4afb-8438-f89a3ee46ad2 Retrieval Hint: "stale PR state checkedAt gameable stop hook live fetch binding merge claim"