feat(flare): page grant_needed, collapse the repeated question, stop claiming health - #252
feat(flare): page grant_needed, collapse the repeated question, stop claiming health#252itsHabib wants to merge 4 commits into
Conversation
|
@claude review |
|
Claude finished @itsHabib's task in 4m 52s —— View job Code Review
|
|
Reviewer: claude |
|
@claude review |
…hten the seams Review round 1 on #252, plus a nit carried from #251. P2 — the digest proposed a generic T2 for a repo whose grant had lapsed, while a single refusal card proposed the ceilings that repo had actually held. Two surfaces giving different advice about the same repo is the bug; Authority now carries ProposedTier/ProposedCycles explicitly, filled from the live grant when there is one and from the widest the repo has ever held when there is not. flare still only proposes what the operator already judged appropriate. P2 — signal_test's hand-rolled itoa zero-padded to two digits, so a single-digit PR number would have written "number":05 and produced invalid JSON far from the failure. strconv.Itoa. P2 — Replay.card takes a pointer receiver: it mutates, and a value receiver said the opposite to anyone reading the call site. P3 — the digest's journal source was the first gate source in config order, so reordering sources would re-page an unchanged picture. It records under flare's own name; uniqueness is the content hash in the id. P3 — markRepeat's write through ev.Fields is named at the declaration. From #251: flare's event KINDS are now constants in internal/event rather than literals on both sides of every branch. Two literals drifting apart does not crash here — it silently stops matching, which for a sink means a card that is never finalized or a page that never renders its class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @itsHabib's task in 3m 40s —— View job Code Review
Reviewed commit: Resolutions from the prior roundAll four named findings from the round-1 review are addressed:
New findings — this roundP3: func tsFor(n int) string { return "170000000." + string(rune('0'+n)) }
P3:
Feature correctness
Reason collapse ( Stall detection ( Journal single-pass replay (
SummaryAll P2 findings from round 1 are resolved. Logic across all three features is sound and the tests pin the real contracts, including the previously incorrect one ( |
|
Reviewer: claude |
Review round 2 on #252, both P3. digest() read time.Now() three times for one logical moment — liveness, the expiry window and the dedupe read must be answered as of the same instant, or a row can be live for one question and lapsed for the next. lifecycle_test's tsFor carried the same rune-arithmetic fragility round 1 removed from itoa: string(rune('0'+n)) leaves the digits at n=10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5fe5a05 to
9df9fa0
Compare
…hten the seams Review round 1 on #252, plus a nit carried from #251. P2 — the digest proposed a generic T2 for a repo whose grant had lapsed, while a single refusal card proposed the ceilings that repo had actually held. Two surfaces giving different advice about the same repo is the bug; Authority now carries ProposedTier/ProposedCycles explicitly, filled from the live grant when there is one and from the widest the repo has ever held when there is not. flare still only proposes what the operator already judged appropriate. P2 — signal_test's hand-rolled itoa zero-padded to two digits, so a single-digit PR number would have written "number":05 and produced invalid JSON far from the failure. strconv.Itoa. P2 — Replay.card takes a pointer receiver: it mutates, and a value receiver said the opposite to anyone reading the call site. P3 — the digest's journal source was the first gate source in config order, so reordering sources would re-page an unchanged picture. It records under flare's own name; uniqueness is the content hash in the id. P3 — markRepeat's write through ev.Fields is named at the declaration. From #251: flare's event KINDS are now constants in internal/event rather than literals on both sides of every branch. Two literals drifting apart does not crash here — it silently stops matching, which for a sink means a card that is never finalized or a page that never renders its class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review round 2 on #252, both P3. digest() read time.Now() three times for one logical moment — liveness, the expiry window and the dedupe read must be answered as of the same instant, or a row can be live for one question and lapsed for the next. lifecycle_test's tsFor carried the same rune-arithmetic fragility round 1 removed from itoa: string(rune('0'+n)) leaves the digits at n=10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7ad1abd to
d1842c9
Compare
…claiming health Three ways the phone rung misinformed the operator. 1. grant_needed was dropped. gate records one per run refused for want of authority — grant_absent, grant_expired, and (since #242) the pre-flight grant_cycle_exceeded — and flare read them and threw them away. It is the ONE alert no agent can act on: it can re-run, re-review and re-judge, but it cannot mint, and neither can the operator from a phone. So it now pages early, as its own card class, carrying the paste-ready `gate grant` at the ceilings the repo already held. flare proposes what has worked before; it never widens and never mints. `flare digest` answers the same question standing rather than one refusal at a time: per repo, what is parked, whether a grant stands, how soon it lapses. Only two situations qualify — parked work with no live grant, and a grant lapsing under parked work — because a digest that lists everything is another wall of text to skim. No pressure produces no card, and an unchanged picture never re-pages (its id is a hash of its content). 2. 318 of 355 parks lead with the identical readiness sentence. Attention to a repeated warning is spent by the second one, so the third onward buys nothing by restating it while what is DIFFERENT is never surfaced. flare fingerprints the LEADING reason clause — gate joins reasons with "; " and the first is the primary one, so fingerprinting the whole line matches almost never (26 of 357) even though the operator reads the same opening sentence every time — counts deliveries per repo over 7 days, and from the third collapses the card: the opener named once, then the PR, tier, head and remaining cycles, then the clauses AFTER the opener, which are the part that is new. Measured: 276 of 357 (77%) would render collapsed. Nothing is suppressed — the collapse changes the card, never whether it is sent. 3. A running loop is not a healthy one. A delivery failure returned nil, so a source wedged behind an undeliverable event reported a clean poll: `sweep` exited 0 and `status` said healthy:true while the ordered cursor blocked every event behind it. The stall is now recorded in cursors.json — when it began, what it is stuck on, how many attempts — `status` reports unhealthy and names it, and a clean poll clears it. Shape: the cycle's fixed context (config, journal, router, courier) and its mutable working set (settled events, live cards, reason counts) each travel as one value instead of as seven arguments, and the journal is replayed ONCE per cycle into all three facts rather than three times (raised on #251). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hten the seams Review round 1 on #252, plus a nit carried from #251. P2 — the digest proposed a generic T2 for a repo whose grant had lapsed, while a single refusal card proposed the ceilings that repo had actually held. Two surfaces giving different advice about the same repo is the bug; Authority now carries ProposedTier/ProposedCycles explicitly, filled from the live grant when there is one and from the widest the repo has ever held when there is not. flare still only proposes what the operator already judged appropriate. P2 — signal_test's hand-rolled itoa zero-padded to two digits, so a single-digit PR number would have written "number":05 and produced invalid JSON far from the failure. strconv.Itoa. P2 — Replay.card takes a pointer receiver: it mutates, and a value receiver said the opposite to anyone reading the call site. P3 — the digest's journal source was the first gate source in config order, so reordering sources would re-page an unchanged picture. It records under flare's own name; uniqueness is the content hash in the id. P3 — markRepeat's write through ev.Fields is named at the declaration. From #251: flare's event KINDS are now constants in internal/event rather than literals on both sides of every branch. Two literals drifting apart does not crash here — it silently stops matching, which for a sink means a card that is never finalized or a page that never renders its class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review round 2 on #252, both P3. digest() read time.Now() three times for one logical moment — liveness, the expiry window and the dedupe read must be answered as of the same instant, or a row can be live for one question and lapsed for the next. lifecycle_test's tsFor carried the same rune-arithmetic fragility round 1 removed from itoa: string(rune('0'+n)) leaves the digits at n=10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d1842c9 to
16b1120
Compare
|
@claude review |
|
Claude finished @itsHabib's task in 2m 31s —— View job Code Review
Reviewed commit: Resolution of round-2 P3 findingsBoth items are addressed in the top commit:
The comment added with the fix makes the invariant explicit: liveness, the expiry window, and the dedupe read must answer the same moment or a row can be live for one question and lapsed for the next. That is the right comment to pin here — it captures the non-obvious constraint that would otherwise invite the skew back in. Completeness check — all prior findings
No new findingsThe top commit is exactly its stated scope — two surgical fixes, no surrounding churn. The The SummaryAll P2 and P3 findings from both prior rounds are resolved. The top commit is minimal, correct, and its intent is clearly documented. No new findings. The branch is clean. |
|
Reviewer: claude |
|
@codex Review this PR, pinned to Sweep of stale open PRs: this one has been open without a complete review panel. Triggering the repository roster on the exact head above so it can be gated. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16b1120a03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ct digest read; stall unplaceable sources; stable digest id - grant_cycle_exceeded pages with NO mint: the ceiling is the stop signal that the review loop ran long, and a paste-ready "-max-cycles used+1" turns it into instructions to keep going. The card explains instead. - lastCeilingsFor proposes the repo's most recent merge grant's own (tier, cycles) tuple, in log order — never the max tier of one grant and the max cycles of another, which is a grant nobody minted. - Grants for any action other than merge are not indexed: they neither cover parked merge work nor lend ceilings to a proposed merge grant. - Authorities builds through strictLedger: a corrupt complete line fails the digest read loudly, like Read, instead of quietly reading as "nothing parked" or "no live grant". - A terminal action that names its subject in its body (already_merged, parented to view evidence) now supersedes the older park for that PR, matching gate's inbox reduction. - A source that cannot be placed is persisted as stalled, so status stops reporting healthy over a source flare has never read. - The digest's dedupe id hashes the stable facts (repo, parked, live, grant id, absolute expiry, proposed ceilings), not the rendered detail whose countdown moves every minute. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Three ways the phone rung misinformed the operator, each verified against the live gate ledger.
1.
grant_neededwas dropped on the floorgate records a
grant_neededartifact every time a run is refused for want of authority —grant_absent,grant_expired, and (since #242) the pre-flightgrant_cycle_exceeded. 18 of them in the live ledger. flare read them and threw them away.It is the ONE alert an agent cannot act on for itself: it can re-run, re-review and re-judge, but it cannot mint — and the operator cannot mint from a phone either. So it must arrive early, while they are still near a keyboard, rather than being discovered later as stalled work.
Now its own card class, carrying the paste-ready remedy:
The ceilings come from what the repo already held — flare proposes what the operator has previously judged appropriate, never widens on its own, and never mints. A spent cycle budget proposes exactly one more cycle than was consumed.
flare digestanswers the same question standing rather than one refusal at a time: per repo, what is parked, whether a grant stands, how soon it lapses. Only two situations qualify — parked work with no live grant (a hard stop) and a grant lapsing under parked work — because a digest that lists everything is another wall of text to skim. No pressure produces no card, and an unchanged picture never re-pages (its dedupe id is a hash of its own content). Parked counting mirrors gate's own inbox reduction: a PR is parked when its latest terminal artifact is an escalation.2. The operator is asked the identical question over and over
318 of 355 parks in the live ledger lead with the identical readiness sentence. Attention to a repeated warning is spent by the second one, so the third onward buys nothing by restating it — while what is DIFFERENT about this park is never surfaced at all.
The fingerprint is the leading reason clause, and that choice is load-bearing. gate joins a park's reasons with
"; "and the first is the primary one, so fingerprinting the whole line almost never matches even though the operator is reading the same opening sentence every time:Past two deliveries for a repo, the card collapses:
Nothing is suppressed. The collapse changes the card, never whether it is sent, and the clauses after the repeated opener — the part that is actually new — are shown rather than hidden. A different reason, or the same sentence about a different repo, never collapses (both pinned by tests).
3.
flare statusclaimed health while nothing was getting throughVerified real, in code and by test.
pollSourcereturnednilon a delivery failure, socyclenever marked the source failed:sweepexited 0 ("swept clean") andstatusreportedhealthy: true— while the ordered cursor sat behind an undeliverable event and every event after it was blocked too.A stall is now recorded in
cursors.json(when it began, the event it is stuck on, attempt count),statusreportshealthy: falseand names it,sweepexits non-zero, and a source that polls cleanly clears its own stall. The retry semantics are unchanged — the cursor still holds, deliberately; only the silence is fixed.This flips one assertion added in #251 (
TestFailedCorrectionRetriesasserted the cycle returned no error) — that test now asserts the honest contract, plus that the stall is recorded and cleared.Shape
Raised on #251 and addressed here rather than deferred: the journal is replayed once per cycle into all three facts it holds (settled events, live cards, reason counts) instead of three separate full scans of a file that only grows. The cycle's fixed context (
runner: config, journal, router, courier) and its mutable working set (cycleState) each travel as one value, which is what keptdispatchfrom growing to seven parameters.Coordination note (concurrent gate work)
No file under
cmd/gate/**and no launchd script is touched.grant_neededis consumed defensively: its two persisted body shapes decode into one struct with absent fields left zero, and a body flare cannot read still pages with less detail.KindGrantNeededis named as the string gate persists, not imported.The additive gate field that would help remains the one recorded in
docs/FOLLOWUPS.md: the park's ceilings on the escalation body.Checks
gofmtclean ·go vet ./...·golangci-lint run ./...(0 issues) ·go test ./...New tests pin: all three refusal reasons paging with a correct mint, the collapse threshold and its negative cases, the digest's content/silence/dedupe-id, and
flare statusexiting 1 while stalled and 0 once delivery recovers.🤖 Generated with Claude Code