Skip to content

Commit 00b9f4e

Browse files
committed
feat: four verified defect fixes, built in parallel worktrees
Four independent slices, each with a test that fails without its fix, each adversarially verified by a separate agent that BROKE the fix and confirmed the test went red. 4 of 4 PASS. 1. THE FILE QUEUE BACKEND HAD NO CRASH RECOVERY. `--reap` was redis-only, so a dead worker's item sat in processing/ forever and an unattended fleet stalled on it. Reproduced before the fix: --reap exited 2, item stranded. file_reap() now requeues items whose mtime is older than the visibility timeout. A FRESH item is never touched -- requeuing a live build means the user's work is built twice. Verifier's mutation 1 (revert the dispatch) reproduced the original defect verbatim: 8 failures, first one naming the stranded item. 17 assertions, no broker needed. Disclosed ceiling, not hidden: mtime-as-claim-time means a consumer that rewrote its own item file would reset the clock and never be reaped. No current consumer does; it is documented in both the header and the function docblock. 2. "ANCHORED 0 of 9" NEEDED TO READ AS HISTORY, NOT A REGRESSION. All 9 receipts predate the base_sha fix and can never be anchored -- that is frozen history, not a live defect, and nothing should try to rewrite them. classify_unanchored() now buckets by_design / historical / regression / live so a genuinely NEW break is distinguishable from old receipts. Verifier ran three mutations; collapsing everything to "historical" (the green-wash direction) trips 4 assertions. 3. THE GATE LADDER HAD NO PROGRAMMATIC SURFACE. `loki gates --json` existed with zero consumers and no dashboard route. Added a read-only endpoint following a sibling GET's auth scope exactly. An UNMEASURED gate serializes as null, never 0 -- zero is a claim that the gate ran and never fired. The agent also found the module docstring documented rationale but not one field, and added the shape. 4. ACCEPTANCE-CRITERION IDS WERE MINTED AND DROPPED. The receipt now records criteria_declared -- the AC ids present in the spec. Named criteria_declared, NOT criteria_met, because we do not measure satisfaction yet and the field name must not imply we do. Absent when the spec has none; never invented. Merge note worth recording: I applied three of the four diffs and missed worktree 2, and the merged tree reported 8 failures in a suite that passed 9/9 in isolation. Running the suites after merging is what caught it -- a "they all passed in their worktrees" summary would have shipped a half-applied change. Gate green 73/0. Four suites: 17 + 9 + 12 + 67 assertions.
1 parent b9f31b7 commit 00b9f4e

10 files changed

Lines changed: 1082 additions & 10 deletions

autonomy/lib/gate_policy.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,29 @@
2020
blocking stays an explicit operator act via the named environment variable,
2121
because a tool that silently starts blocking is the thing operators most
2222
reasonably fear.
23+
24+
Shape (assess() and --json, schema_version 1). This is the contract the
25+
dashboard endpoint GET /api/gate-policy and tests/test_gate_policy_endpoint.py
26+
both read against, so field names here are load-bearing:
27+
28+
schema_version int 1
29+
status str "measured"
30+
ledger str "present" | "absent" -- whether the per-gate failure
31+
ledger .loki/quality/gate-failure-count.json was read
32+
gates list one record per known gate, blocking gates first, each
33+
group sorted by name:
34+
gate str gate name (e.g. "code_review")
35+
mode str "blocking" | "advisory" -- for a promotable gate this
36+
depends on the ENVIRONMENT at call time
37+
promotable bool True when a real promotion knob exists in run.sh
38+
audit_hits int|null failures counted for this gate. null means
39+
UNMEASURED -- no ledger, or no entry for this gate.
40+
NEVER 0 for an unmeasured gate: 0 is the positive claim
41+
that the gate ran and never fired, which is the false
42+
green an absent measurement always produces.
43+
why str one-line description of what the gate checks
44+
promote_with str|null "VAR=value" to make an advisory gate blocking; null
45+
when the gate already blocks
2346
"""
2447

2548
import json

autonomy/lib/outcome_ledger.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,92 @@
9393
"diff_range_mismatch": "the base..head diff does not match the receipt's file set",
9494
}
9595

96+
# HISTORICAL vs LIVE: the distinction that stops an old receipt from reading as
97+
# a regression.
98+
#
99+
# `ANCHORED 0 of 9` is alarming until you know WHY. Two very different things
100+
# produce it, and a flat count of reasons cannot tell them apart:
101+
#
102+
# FROZEN The receipt file itself lacks the anchor. base_sha was written as
103+
# "" and that JSON is on disk forever. No future fix can anchor it,
104+
# and rewriting a receipt to make a metric look better is the exact
105+
# dishonesty this module exists to refuse. These are HISTORY.
106+
# LIVE The receipt records real shas; only the ENVIRONMENT cannot resolve
107+
# them right now -- a shallow clone, an unmerged branch, or a file
108+
# set that still has uncommitted edits. These can anchor later, with
109+
# no change to the receipt.
110+
#
111+
# Measured on this repo: all 9 receipts are frozen (8 base_sha_empty, 1
112+
# greenfield_no_baseline) and were generated 2026-07-27 and 2026-07-31, BEFORE
113+
# proof-generator learned to read .loki/state/start-sha (commit 99ce689d,
114+
# 2026-08-07). So the zero is fully explained by history.
115+
# Frozen because the GENERATOR failed to record something it should have. These
116+
# are the only two where recency is meaningful: before the fix they are history,
117+
# after it they are a live bug in proof-generator.
118+
FROZEN_GENERATOR_REASONS = frozenset({
119+
"head_sha_empty",
120+
"base_sha_empty",
121+
})
122+
123+
# Frozen BY DESIGN, and correct at any date. A genuinely greenfield repo has no
124+
# earlier commit to diff against, and a receipt written over uncommitted work
125+
# honestly has base == head. Neither can ever anchor, and neither is a defect --
126+
# so recency must NOT be applied to them. Calling a correct greenfield receipt a
127+
# regression would be a false alarm on the signal added to prevent false alarms.
128+
FROZEN_BY_DESIGN_REASONS = frozenset({
129+
"greenfield_no_baseline",
130+
"change_not_committed",
131+
})
132+
133+
FROZEN_REASONS = FROZEN_GENERATOR_REASONS | FROZEN_BY_DESIGN_REASONS
134+
135+
# The two sets must stay disjoint. If a reason appeared in both, classification
136+
# would depend on which branch is checked first -- and a by-design case that
137+
# drifted into the generator set would start alarming as a regression the moment
138+
# someone reordered the checks. Assert the invariant rather than trusting order.
139+
assert not (FROZEN_GENERATOR_REASONS & FROZEN_BY_DESIGN_REASONS), \
140+
"a reason cannot be both a generator failure and correct by design"
141+
142+
# The commit that taught proof-generator to resolve base_sha from
143+
# .loki/state/start-sha when the env var is absent. A receipt generated at or
144+
# after this instant should carry a real baseline, so a FROZEN reason on one is
145+
# NOT history -- it is a live regression in the generator.
146+
#
147+
# Recency is the discriminator because it is the only one the receipts actually
148+
# support: they carry generated_at (verified on all 9), and loki_version tracks
149+
# releases rather than this fix. A frozen-vs-live split ALONE would file a newly
150+
# broken receipt under history, which is the green-wash this guards against.
151+
BASE_SHA_FIX_UTC = "2026-08-07T13:39:32Z" # 99ce689d, committed 09:39:32 -04:00
152+
153+
154+
def classify_unanchored(reason, generated_at):
155+
"""Bucket an unanchored receipt: historical, regression, or live.
156+
157+
Returns one of:
158+
"by_design" -- unanchorable and CORRECT: a greenfield run with no earlier
159+
commit, or a receipt over uncommitted work. Never a defect,
160+
at any date, so recency is not applied.
161+
"historical" -- the generator failed to record an anchor, in a receipt
162+
written BEFORE the fix. History, and never anchorable now.
163+
"regression" -- the same generator failure AFTER the fix. The anchor is
164+
being dropped again. This is the only bucket that alarms.
165+
"live" -- the receipt is fine; the environment cannot resolve it yet.
166+
"""
167+
if reason in FROZEN_BY_DESIGN_REASONS:
168+
return "by_design"
169+
if reason not in FROZEN_GENERATOR_REASONS:
170+
return "live"
171+
# No timestamp means we cannot place it relative to the fix. Refuse to call
172+
# it history, because that is the direction that hides a regression.
173+
#
174+
# ponytail: lexicographic compare, correct only for the "...Z" ISO form the
175+
# generator writes (verified on all 9 receipts). An offset-form timestamp
176+
# would sort below the constant and read as historical -- the hiding
177+
# direction. Parse properly only if a generator ever emits offsets.
178+
if not generated_at:
179+
return "regression"
180+
return "historical" if generated_at < BASE_SHA_FIX_UTC else "regression"
181+
96182

97183
def _git(args, cwd, timeout=30):
98184
"""Run a git command read-only. Returns (rc, stdout). Never raises.
@@ -315,6 +401,9 @@ def outcome_for_receipt(proof_path, cwd):
315401
state, reason = resolve_anchor(base_sha, head_sha, files, cwd)
316402
rec["anchor"] = {"state": state, "reason": reason}
317403
if state != "anchored":
404+
# An old receipt is not a regression. Classify so a reader can tell a
405+
# frozen pre-fix receipt from a generator that started dropping anchors.
406+
rec["anchor"]["klass"] = classify_unanchored(reason, rec["generated_at"])
318407
rec["outcome"] = UNKNOWN
319408
rec["reason"] = ANCHOR_REASONS.get(reason, reason or "not anchored")
320409
rec["commands"] = [
@@ -381,18 +470,28 @@ def summarize(records):
381470
# as "no data" instead of "your receipts are not recording a landed sha",
382471
# which is an actionable defect.
383472
reasons = {}
473+
klasses = {"by_design": 0, "historical": 0, "regression": 0, "live": 0}
384474
for r in records:
385475
a = r.get("anchor") or {}
386476
if a.get("state") and a["state"] != "anchored":
387477
key = a.get("reason") or "unknown"
388478
reasons[key] = reasons.get(key, 0) + 1
479+
k = a.get("klass")
480+
if k in klasses:
481+
klasses[k] += 1
389482

390483
summary = {
391484
"receipts_total": total,
392485
"receipts_measured": len(measured),
393486
"receipts_unknown": len(unknown),
394487
"reverted": len(reverted),
395488
"unanchored_reasons": reasons,
489+
# The count that turns an alarming zero into an explained one. A
490+
# regression here is the only bucket that warrants action.
491+
"unanchored_by_design": klasses["by_design"],
492+
"unanchored_historical": klasses["historical"],
493+
"unanchored_regression": klasses["regression"],
494+
"unanchored_live": klasses["live"],
396495
}
397496
if measured:
398497
summary["change_failure_rate"] = round(len(reverted) / len(measured), 4)
@@ -442,6 +541,29 @@ def render_text(records, summary, note=None):
442541
out.append(" Why not anchored (a receipt must prove base..head IS the change):")
443542
for k, n in sorted(reasons.items(), key=lambda kv: -kv[1]):
444543
out.append(f" {k:24} {n:3} {ANCHOR_REASONS.get(k, '')}")
544+
545+
# Without this, ANCHORED 0 of N reads as a regression when it is history.
546+
hist = summary.get("unanchored_historical", 0)
547+
regr = summary.get("unanchored_regression", 0)
548+
live = summary.get("unanchored_live", 0)
549+
design = summary.get("unanchored_by_design", 0)
550+
if hist or regr or live or design:
551+
out.append("")
552+
if design:
553+
out.append(f" {design} by design: a greenfield run or uncommitted"
554+
f" work has no baseline to diff against. Correct, not a"
555+
f" defect, and never anchorable.")
556+
if hist:
557+
out.append(f" {hist} historical: written before the base_sha fix"
558+
f" ({BASE_SHA_FIX_UTC[:10]}); the receipt itself has no"
559+
f" baseline, so these can never anchor. Not a defect.")
560+
if live:
561+
out.append(f" {live} live: the receipt records real shas; this"
562+
f" clone cannot resolve them yet. May anchor later.")
563+
if regr:
564+
out.append(f" {regr} REGRESSION: written AFTER the fix and still"
565+
f" missing a baseline. The generator is dropping the"
566+
f" anchor -- this one is a defect.")
445567
out.append("")
446568
cfr = summary.get("change_failure_rate")
447569
if cfr == UNKNOWN:

autonomy/lib/proof-generator.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,8 +1024,33 @@ def _collect_iterations(loki_dir):
10241024
return {"count": count, "succeeded": n_completed, "failed": n_failed}
10251025

10261026

1027+
# Acceptance-criterion ids as minted at intake ("- AC-<AXIS>-NNN: <text>", see
1028+
# _brief_acceptance_criteria in autonomy/loki). Anchored and strict on purpose:
1029+
# a loose pattern would count prose that merely mentions an id, and the whole
1030+
# value of the id is that a citation points at exactly one criterion.
1031+
_AC_ID_RE = re.compile(r"^- (AC-[A-Z]+-[0-9]{3}): ", re.MULTILINE)
1032+
1033+
1034+
def _spec_criteria_declared(text):
1035+
"""Return the acceptance-criterion ids the spec DECLARES, in spec order.
1036+
1037+
DECLARED, NOT SATISFIED. This records which criteria exist in the spec and
1038+
nothing more -- no check runs here, and no field in the receipt asserts that
1039+
any of these was met. Naming it criteria_met would be a lie we cannot back.
1040+
1041+
Empty list when the spec declares none (an older PRD, a hand-written spec,
1042+
or a run with no spec file at all). Never invented, never a placeholder.
1043+
"""
1044+
if not text:
1045+
return []
1046+
# Deduped: an id is a citation target, so each must resolve to one criterion.
1047+
# A repeated id is a spec bug; counting it twice would not make it citable.
1048+
return list(dict.fromkeys(_AC_ID_RE.findall(text)))
1049+
1050+
10271051
def _collect_spec(loki_dir, target_dir):
1028-
"""Return spec dict {source, brief}. brief truncated to 600 chars."""
1052+
"""Return spec dict {source, brief, criteria_declared}. brief truncated to
1053+
600 chars."""
10291054
prd_path = os.environ.get("PRD_PATH", "").strip()
10301055
source = ""
10311056
brief = ""
@@ -1052,7 +1077,15 @@ def _collect_spec(loki_dir, target_dir):
10521077
# Full brief here; the <=600 cap is applied AFTER redaction in generate()
10531078
# so a secret straddling the cap cannot be sliced into an under-length
10541079
# fragment that bypasses the redactor.
1055-
return {"source": source, "brief": brief}
1080+
#
1081+
# Criteria are parsed from the FULL spec text, not from the 600-char display
1082+
# cap: a PRD's acceptance-criteria block sits well past char 600, so parsing
1083+
# the capped brief would silently drop most of them.
1084+
return {
1085+
"source": source,
1086+
"brief": brief,
1087+
"criteria_declared": _spec_criteria_declared(brief),
1088+
}
10561089

10571090

10581091
def _self_version():

0 commit comments

Comments
 (0)