You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ledger(policy): require a declared reason for any repeat verdict at one head SHA (#9865)
* ledger(policy): require a declared reason for any repeat verdict at one head SHA (#9742)
decision_records already kept every evaluation of a (repo, pull, head_sha) -- a repeat
lands as <id>:revN, so nothing was ever silently replaced. What the record could not say
is WHY a second evaluation happened, which verdict it supersedes, or who asked for it:
"evaluated once" and "evaluated three times and one result was kept" were
indistinguishable in the public record.
The write layer now refuses a repeat verdict that does not declare a reason, so no caller
can bypass it by writing the row itself. A new head SHA is untouched -- that is a fresh
verdict by definition and needs no reason.
The reason is DERIVED, not judged per call site. The delivery-id prefixes that already
partitioned job producers were scattered string literals with matching startsWith reads
elsewhere; they now live in one module, and the reason map is typed
Record<DeliveryIdOrigin, ReevaluationReason> -- adding a producer without assigning a
reason fails the build rather than writing an unexplained verdict.
scheduled_recheck names the routine sweep. It is by far the highest-volume cause, and
without it every sweep tick past the first would have had to borrow one of the
incident-shaped codes -- drowning them, and (since both call sites swallow a throw) doing
it silently while aborting the rest of the maintenance pass.
DecisionRecordContext.reevaluation is required rather than defaulted: a repeat verdict
whose cause nobody declared is exactly what this refuses, so a future call site must
answer it at the type level instead of inheriting a plausible-looking guess.
* fix(ci): point the re-gate sort-key allowlist at the manual-regate ORIGIN key
The allowlist matches a producer by a marker string inside its object literal. The
manual re-gate route no longer repeats the `manual-regate:` prefix literal -- it calls
deliveryIdFor("manualRegate", ...) -- so the marker moved with it.
* refactor(queue): assert the delivery-id prefixes are distinguishable, not longest-match
The longest-prefix tie-break could only run if one prefix were a prefix of another,
which none is -- untestable defensive code standing in for a guarantee nobody stated.
The guarantee is now an invariant test, so adding an ambiguous prefix fails with the
real problem named rather than being silently absorbed by a scan.
"The maintainer-triggered manual re-gate route enqueues at priority 99 to jump the queue ON PURPOSE — an operator asking for one PR now is exactly the case oldest-first should not apply to. It also has no PR record in hand (the body carries only repoFullName + prNumber).",
0 commit comments