From 4cae6f44cfa9e38c415b3b7fae43b667e16e37cb Mon Sep 17 00:00:00 2001 From: Aid Idrizovic Date: Sat, 20 Jun 2026 23:38:29 -0500 Subject: [PATCH 1/2] trace: enrich v0 with blocker_codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds blocker_codes (sorted, unique string[]) to every trace artifact — the distinct blocker codes (E_*) seen across the run. This is the field the upcoming bean-lessons analyzer needs to rank which gate conditions fire most (weak load-bearing, open risks, conflicts, verifier failures); trace v0 previously stored only blocker COUNTS. - bean-run: derive blocker_codes from the open-front keys; emit alongside the counts. - schemas/trace.schema.json: new required key, array of unique strings; top level stays additionalProperties:false. - conformance: asserts the field exists, is a sorted unique string[], and contains the expected E_OPEN_RISK for the driver fixture. - trace.md updated. trace v0 is unreleased, so no migration. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- rs/src/bin/bean-run.rs | 10 ++++++++++ schemas/trace.schema.json | 7 +++++++ skills/bean/references/trace.md | 33 +++++++++++++++++---------------- test/conformance.mjs | 13 ++++++++++++- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2375443..67295c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ - **Trace artifact v0 (emit-only).** Every `bean-run` now writes a stable post-run record to `.bean/runs/.json`: `schema_version`, `run_id`, `goal`, `started_at`/`ended_at`, `status`, `certificate`, `rounds`, `pivot_count`, `blockers_opened`/`blockers_closed`, - `verifier_verdicts`, `residuals`, `artifacts_changed`, `metadata`. One file per run (not a + `blocker_codes`, `verifier_verdicts`, `residuals`, `artifacts_changed`, `metadata`. One file per run (not a rolling file), so future tooling can analyze a corpus of runs without scraping transcripts. The top-level shape is **fixed** (`additionalProperties: false`); additive fields go under the `metadata` hatch. Emission **fails closed**: if the trace can't be written, `bean-run` exits diff --git a/rs/src/bin/bean-run.rs b/rs/src/bin/bean-run.rs index 0afa5fd..b5ca92c 100644 --- a/rs/src/bin/bean-run.rs +++ b/rs/src/bin/bean-run.rs @@ -433,6 +433,15 @@ fn main() { .iter() .filter(|k| !final_blockers.contains(*k)) .count(); + // the distinct blocker CODES (E_*) seen across the run — sorted+unique via BTreeSet — so the + // trace analyzer can rank which gate conditions fire most. Derived from the code:claim keys. + let blocker_codes: Vec = blockers_seen + .iter() + .filter_map(|k| k.split_once(':').map(|(c, _)| c.to_string())) + .filter(|c| !c.is_empty()) + .collect::>() + .into_iter() + .collect(); let pivot_count = trace .iter() .filter(|t| t.get("pivot").and_then(|v| v.as_bool()).unwrap_or(false)) @@ -479,6 +488,7 @@ fn main() { "pivot_count": pivot_count, "blockers_opened": blockers_seen.len(), "blockers_closed": blockers_closed, + "blocker_codes": blocker_codes, "verifier_verdicts": verifier_verdicts, "residuals": residuals, "artifacts_changed": Vec::::new(), diff --git a/schemas/trace.schema.json b/schemas/trace.schema.json index 9a7d325..887490f 100644 --- a/schemas/trace.schema.json +++ b/schemas/trace.schema.json @@ -16,6 +16,7 @@ "pivot_count", "blockers_opened", "blockers_closed", + "blocker_codes", "verifier_verdicts", "residuals", "artifacts_changed" @@ -68,6 +69,12 @@ "minimum": 0, "description": "Of those, the count not present in the final signal." }, + "blocker_codes": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Sorted, de-duplicated blocker codes (E_*) seen across the run, so analyzers can rank which gate conditions fire most." + }, "verifier_verdicts": { "type": "array", "description": "The scrubbed verdicts bean-verify deposited under .bean/verdicts/, embedded verbatim.", diff --git a/skills/bean/references/trace.md b/skills/bean/references/trace.md index 6c85535..78b66e4 100644 --- a/skills/bean/references/trace.md +++ b/skills/bean/references/trace.md @@ -16,22 +16,23 @@ start there. Schema: [`schemas/trace.schema.json`](../../../schemas/trace.schema.json) (`schema_version: "trace/v0"`). -| Field | Meaning | -| ------------------------- | ---------------------------------------------------------------------------------- | -| `schema_version` | `"trace/v0"` — bump on any breaking shape change. | -| `run_id` | Unique per run (`run-`); also the filename stem. | -| `goal` | The run goal from `run.json`. | -| `started_at` / `ended_at` | Unix epoch milliseconds. | -| `status` | Final outcome: `ready` / `converged-with-residuals` / `budget-exceeded` / `stuck`. | -| `certificate` | Final convergence certificate from `bean-check`. | -| `rounds` | Driver rounds executed. | -| `pivot_count` | No-progress rounds turned into pivots. | -| `blockers_opened` | Distinct open fronts (`code:claim`) seen across the run. | -| `blockers_closed` | Of those, how many are absent from the final signal. | -| `verifier_verdicts` | The scrubbed verdicts from `.bean/verdicts/`, embedded verbatim. | -| `residuals` | Claims tagged `residual`, each `{id, reason}` (reason = the claim content). | -| `artifacts_changed` | Files the run changed, when available. v0 does not track this yet → `[]`. | -| `metadata` | Extension hatch (`{}` by default). Additive/experimental fields go here. | +| Field | Meaning | +| ------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `schema_version` | `"trace/v0"` — bump on any breaking shape change. | +| `run_id` | Unique per run (`run-`); also the filename stem. | +| `goal` | The run goal from `run.json`. | +| `started_at` / `ended_at` | Unix epoch milliseconds. | +| `status` | Final outcome: `ready` / `converged-with-residuals` / `budget-exceeded` / `stuck`. | +| `certificate` | Final convergence certificate from `bean-check`. | +| `rounds` | Driver rounds executed. | +| `pivot_count` | No-progress rounds turned into pivots. | +| `blockers_opened` | Distinct open fronts (`code:claim`) seen across the run. | +| `blockers_closed` | Of those, how many are absent from the final signal. | +| `blocker_codes` | Sorted, unique blocker codes (`E_*`) seen across the run — lets analyzers rank which gate conditions fire most. | +| `verifier_verdicts` | The scrubbed verdicts from `.bean/verdicts/`, embedded verbatim. | +| `residuals` | Claims tagged `residual`, each `{id, reason}` (reason = the claim content). | +| `artifacts_changed` | Files the run changed, when available. v0 does not track this yet → `[]`. | +| `metadata` | Extension hatch (`{}` by default). Additive/experimental fields go here. | ## Stability rules diff --git a/test/conformance.mjs b/test/conformance.mjs index 51a9a65..69254e5 100644 --- a/test/conformance.mjs +++ b/test/conformance.mjs @@ -335,6 +335,7 @@ console.log(`${dpass}/3 driver smoke checks pass`); "pivot_count", "blockers_opened", "blockers_closed", + "blocker_codes", "verifier_verdicts", "residuals", "artifacts_changed", @@ -346,6 +347,15 @@ console.log(`${dpass}/3 driver smoke checks pass`); const t = JSON.parse(fs.readFileSync(path.join(runsDir, files[0]), "utf8")); const missing = ALLOWED.filter((k) => !(k in t)); const unknown = Object.keys(t).filter((k) => !ALLOWED.includes(k)); + const bc = t.blocker_codes; + // blocker_codes must be a sorted, unique string[] and (for this fixture, where round 1 + // has an open risk) contain E_OPEN_RISK. + const bcOk = + Array.isArray(bc) && + bc.every((x) => typeof x === "string") && + JSON.stringify(bc) === JSON.stringify([...bc].sort()) && + new Set(bc).size === bc.length && + bc.includes("E_OPEN_RISK"); const shapeOk = t.schema_version === "trace/v0" && missing.length === 0 && @@ -355,12 +365,13 @@ console.log(`${dpass}/3 driver smoke checks pass`); Array.isArray(t.verifier_verdicts) && Array.isArray(t.residuals) && Array.isArray(t.artifacts_changed) && + bcOk && typeof t.metadata === "object" && !Array.isArray(t.metadata); ok = shapeOk; why = shapeOk ? "" - : `schema_version=${t.schema_version} missing=[${missing}] unknown=[${unknown}] status=${t.status} vs ${report.outcome}`; + : `schema_version=${t.schema_version} missing=[${missing}] unknown=[${unknown}] blocker_codes=${JSON.stringify(bc)} status=${t.status} vs ${report.outcome}`; } if (ok) { console.log(" ok trace artifact v0 written with stable shape"); From 6cf4328606d4670dcf06a50d5c8b21a7eb655f42 Mon Sep 17 00:00:00 2001 From: Aid Idrizovic Date: Sat, 20 Jun 2026 23:41:58 -0500 Subject: [PATCH 2/2] trace: clarify blocker_codes is the observed-during-run set (incl. closed), not final-state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review of #3: make the docs/schema explicit that blocker_codes is the set of distinct codes seen across all rounds — including blockers later closed — not a count and not final-state-only. Code already behaves this way (derived from blockers_seen, not final). Co-Authored-By: Claude Opus 4.8 (1M context) --- schemas/trace.schema.json | 2 +- skills/bean/references/trace.md | 34 ++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/schemas/trace.schema.json b/schemas/trace.schema.json index 887490f..c5a85b5 100644 --- a/schemas/trace.schema.json +++ b/schemas/trace.schema.json @@ -73,7 +73,7 @@ "type": "array", "items": { "type": "string" }, "uniqueItems": true, - "description": "Sorted, de-duplicated blocker codes (E_*) seen across the run, so analyzers can rank which gate conditions fire most." + "description": "The set of distinct blocker codes (E_*) OBSERVED DURING the run, including ones whose blockers were later closed — sorted, de-duplicated. Not a count, not final-state-only. Lets analyzers rank which gate conditions recur." }, "verifier_verdicts": { "type": "array", diff --git a/skills/bean/references/trace.md b/skills/bean/references/trace.md index 78b66e4..abb2e33 100644 --- a/skills/bean/references/trace.md +++ b/skills/bean/references/trace.md @@ -16,23 +16,23 @@ start there. Schema: [`schemas/trace.schema.json`](../../../schemas/trace.schema.json) (`schema_version: "trace/v0"`). -| Field | Meaning | -| ------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `schema_version` | `"trace/v0"` — bump on any breaking shape change. | -| `run_id` | Unique per run (`run-`); also the filename stem. | -| `goal` | The run goal from `run.json`. | -| `started_at` / `ended_at` | Unix epoch milliseconds. | -| `status` | Final outcome: `ready` / `converged-with-residuals` / `budget-exceeded` / `stuck`. | -| `certificate` | Final convergence certificate from `bean-check`. | -| `rounds` | Driver rounds executed. | -| `pivot_count` | No-progress rounds turned into pivots. | -| `blockers_opened` | Distinct open fronts (`code:claim`) seen across the run. | -| `blockers_closed` | Of those, how many are absent from the final signal. | -| `blocker_codes` | Sorted, unique blocker codes (`E_*`) seen across the run — lets analyzers rank which gate conditions fire most. | -| `verifier_verdicts` | The scrubbed verdicts from `.bean/verdicts/`, embedded verbatim. | -| `residuals` | Claims tagged `residual`, each `{id, reason}` (reason = the claim content). | -| `artifacts_changed` | Files the run changed, when available. v0 does not track this yet → `[]`. | -| `metadata` | Extension hatch (`{}` by default). Additive/experimental fields go here. | +| Field | Meaning | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `schema_version` | `"trace/v0"` — bump on any breaking shape change. | +| `run_id` | Unique per run (`run-`); also the filename stem. | +| `goal` | The run goal from `run.json`. | +| `started_at` / `ended_at` | Unix epoch milliseconds. | +| `status` | Final outcome: `ready` / `converged-with-residuals` / `budget-exceeded` / `stuck`. | +| `certificate` | Final convergence certificate from `bean-check`. | +| `rounds` | Driver rounds executed. | +| `pivot_count` | No-progress rounds turned into pivots. | +| `blockers_opened` | Distinct open fronts (`code:claim`) seen across the run. | +| `blockers_closed` | Of those, how many are absent from the final signal. | +| `blocker_codes` | The **set of distinct blocker codes (`E_*`) observed during the run, including ones whose blockers were later closed** — sorted, unique. Not a count, not final-state-only; lets analyzers rank which gate conditions recur. | +| `verifier_verdicts` | The scrubbed verdicts from `.bean/verdicts/`, embedded verbatim. | +| `residuals` | Claims tagged `residual`, each `{id, reason}` (reason = the claim content). | +| `artifacts_changed` | Files the run changed, when available. v0 does not track this yet → `[]`. | +| `metadata` | Extension hatch (`{}` by default). Additive/experimental fields go here. | ## Stability rules