Skip to content

Commit d1791c0

Browse files
committed
docs: simplify M2 to taxonomy-only; defer emit-site migration (§16)
The M6 fold reads evidence events via the M2-T001 reader, which maps from the audit JSONL — so a mapper entry (M2-T002) is sufficient to surface each event, regardless of how it was written. Routing emission through the spine (old M2-T003) is unnecessary for the fold and is the cross-component step that produced the double-write (§15). M2 is now M2-T001 (done) + M2-T002 (taxonomy/mapper, additive); emit migration deferred to the component milestones (M4/M5/M7) as M1-style replaces. Constraint: docs only; narrows M2 scope to remove the double-write surface; no code change Tested: docs validator 0 errors Confidence: high Roadmap-Status: unchanged
1 parent 13faf4f commit d1791c0

2 files changed

Lines changed: 48 additions & 42 deletions

File tree

docs/generated/docs-inventory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ Do not edit this file manually — regenerate instead.
414414
| `ops/security-hardening.md` | working | 11733 | `0a385c7dab82` |
415415
| `ops/troubleshooting.md` | working | 9127 | `4921b6d50f5c` |
416416
| `permission-and-approval-playbook.md` | working | 6560 | `813bc74bb156` |
417-
| `plans/adr-0032-m1-m6-work-plan-2026-06-13.md` | archive | 48929 | `002ea553fa1d` |
417+
| `plans/adr-0032-m1-m6-work-plan-2026-06-13.md` | archive | 49129 | `c1afb6a5bde8` |
418418
| `plans/agent-ecosystem-acceptance-roadmap-2026-05-31.md` | archive | 29099 | `7c4a4972cfeb` |
419419
| `plans/community-pain-points-response-plan-2026-06-05.md` | archive | 7276 | `571d010133ad` |
420420
| `plans/competitive-positioning-plan-2026-05-31.md` | archive | 8726 | `d16dfd2bdd99` |

docs/plans/adr-0032-m1-m6-work-plan-2026-06-13.md

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ Every phase must preserve:
8888
M0 accepted ADR + dual-write spine [done]
8989
-> M1 audit as consumer [done]
9090
-> M2 evidence-event taxonomy coverage (NEW; was missing)
91-
type + map + migrate-to-emit (replace audit.record, write once;
92-
NOT dual-write — §15) every audit event the evidence bundle reads
93-
(routes, git-sandbox, skills, tests, undo, provenance,
94-
approval/tool-call decision events, cancelled/pending lifecycle)
91+
type + map (both directions) every audit event the evidence bundle
92+
reads — reader surfaces them FROM the audit JSONL; emit-site
93+
migration deferred to component milestones (§16). Covers routes,
94+
git-sandbox, skills, tests, undo, provenance, approval/tool-call
95+
decision events, cancelled/pending lifecycle
9596
-> M3 plan gate interceptor (parity-first)
9697
-> M4 approval gate then budget gate (parity-first)
9798
-> M5 HookRegistry on spine
@@ -159,7 +160,7 @@ consumers by M6.
159160
| Milestone | Exit criteria |
160161
| --- | --- |
161162
| ADR-0032-M1 | AuditLogger can consume RunEvents and produce byte-equivalent JSONL for golden proof runs; legacy call sites delegate instead of directly owning serialization decisions. |
162-
| ADR-0032-M2 (REDEFINED) | Every audit event the evidence bundle reads is typed in `RunEventType`, mapped both directions, and its call site **migrated** from `audit.record` to a single spine `emit` (replace, not dual-write — §15) so the consumer writes it exactly once; verified by M1-style byte-equivalence and per-event-type count parity. Covers routes, git-sandbox, skills, tests, undo, provenance, the approval/tool-call decision events, and the cancelled/pending lifecycle. Read side (`read_run_events_from_*`) surfaces them. Zero behavior change. (Old M2 "evidence/receipt fold" moved to M6 — see §14.) |
163+
| ADR-0032-M2 (REDEFINED, taxonomy-only §16) | Every audit event the evidence bundle reads is typed in `RunEventType` and mapped both directions, so the M2-T001 reader surfaces it **from the audit JSONL** (mapper is sufficient; emit-site migration is NOT in M2 — it is deferred to the component milestones, §16). Covers routes, git-sandbox, skills, tests, undo, provenance, approval/tool-call decision events, cancelled/pending lifecycle. Pure additive; zero behavior change. (Old M2 "evidence/receipt fold" moved to M6 — §14.) |
163164
| ADR-0032-M3 | Plan gate is an interceptor using `PlanValidator`, landed parity-first (§13.3): a shadow-parity test asserting interceptor==inline per reason code went green before the inline branch was deleted in a separate commit. Denials and reason codes match current behavior; adversarial and first-hour tests remain green. |
164165
| ADR-0032-M4 | Approval gate then budget gate are interceptors, each landed parity-first in two commits (shadow parity green → enforce+delete); budget done last and alone. `pending_approval`, resume, scoped approvals, budget warnings, and budget-exhausted behavior remain unchanged. Decision events keep the contract typed in M2. |
165166
| ADR-0032-M5 | HookRegistry subscribes through the spine; Claude-Code-compatible hook names remain aliases; public hook API docs and tests pass. |
@@ -334,42 +335,24 @@ consumers by M6.
334335
`tests/lifecycle/test_run_event_spine.py` (mapper exhaustiveness).
335336
- Risk: low (pure mapping). Parallelizable: no. Human Review Required: no.
336337

337-
### ADR32-M2-T003: Straight-Migrate Evidence Events to Spine Emit (NOT dual-write)
338-
339-
> Re-sequenced (§14); **corrected 2026-06-13 after a verified double-write
340-
> regression** — see §15. The earlier wording said "dual-write: add emit, do
341-
> not remove audit.record". That is WRONG post-M1: M1 made `AuditLogger` a
342-
> *spine consumer*, so a single `event_spine.emit(X)` already produces the
343-
> `audit.record` for X. Keeping the original `audit.record(X)` AND adding
344-
> `emit(X)` writes the audit entry twice (empirically: `tool_call_started`
345-
> appeared 2× and inflated `destructive_tool_calls`). Receipt work moved to
346-
> `ADR32-FOLD-T002`.
347-
348-
- Goal: migrate each M2-T002 evidence event to the spine the way M1 migrated
349-
the 8 lifecycle events — **replace** `audit.record('x', …)` with
350-
`event_spine.emit(RunEventType.X, …)` so the audit consumer writes it exactly
351-
once.
352-
- Scope: one event family at a time; at each site, delete the `audit.record`
353-
call and emit the typed event instead. No gate logic moves here (M3/M4).
354-
- Inputs: M2-T002 mapper; the audit call sites across runner/CLI/approval/skill
355-
paths.
356-
- Outputs: each evidence-event site emits through the spine; no direct
357-
`audit.record` remains for migrated event types.
358-
- Dependencies: ADR32-M2-T002.
359-
- Authority / Data Boundary: zero behavior change; exactly-once audit write.
360-
- Acceptance Criteria:
361-
- **Each migrated event appears exactly once** in the audit log for a run
362-
that triggers it (regression guard: count per event_type unchanged vs
363-
pre-M2 baseline — `destructive_tool_calls` etc. must NOT change).
364-
- Byte-equivalence (M1 normalized-diff method) for a scenario exercising
365-
routes, a git sandbox, a skill load, a test run, an approval, and an undo.
366-
- A frozen contract test (like `test_m1_audit_stream_matches_frozen_contract`)
367-
covers the extended event set.
368-
- Full acceptance tier green; smoke green; **no acceptance assertion is
369-
edited to match a new count** (a changed count means a double-write bug).
370-
- Tests: `tests/lifecycle/`, `tests/acceptance/test_run_evidence_summary_flow.py`.
371-
- Risk: medium (many call sites). Parallelizable: no.
372-
Human Review Required: no.
338+
### ADR32-M2-T003: DROPPED from M2 — emit-site migration deferred to component milestones
339+
340+
> **Removed from M2 scope 2026-06-13 (§16).** Two findings collapsed this task:
341+
> (1) the double-write regression (§15) showed that adding `emit` while keeping
342+
> `audit.record` post-M1 writes the audit entry twice; (2) more importantly, the
343+
> M6 fold does **not** need these events spine-*emitted* at all — the M2-T001
344+
> reader maps from the **audit JSONL**, so a mapper entry (M2-T002) is
345+
> sufficient for the reader to surface them regardless of how they were written.
346+
>
347+
> Migrating each evidence event's emit site to the spine is therefore a pure
348+
> architectural-purity step with cross-component spine-threading risk
349+
> (`approval_manager`, `skill_lifecycle`, `sandbox_resolution`, CLI). It is
350+
> **deferred to the milestone that already moves each owning component onto the
351+
> spine**: approval/tool-call decision events with M4, skill/route/sandbox/undo
352+
> emission with M5/M7's inline-eventing removal. When done there, each is a
353+
> *replace* (M1 pattern), never a dual-write, with exactly-once + count parity.
354+
>
355+
> **M2 is now just M2-T001 (reader, done) + M2-T002 (taxonomy/mapper).**
373356
374357
### ADR32-M3-T001: Plan Interceptor Contract
375358

@@ -1013,3 +996,26 @@ edited assertions restored to their original counts.
1013996
(`destructive_tool_calls`, tool-call counts, audit line counts) changes, treat
1014997
it as a double-write/behavior-change bug to fix at the source — never edit the
1015998
assertion to match.
999+
1000+
---
1001+
1002+
## 16. M2 Simplified to Taxonomy-Only — 2026-06-13
1003+
1004+
After the §15 double-write regression, a closer look showed the emit-site
1005+
migration (old M2-T003) was **over-scoped and unnecessary for the M6 fold**:
1006+
1007+
- The M2-T001 reader maps `event_type` strings **read from the audit JSONL**
1008+
into `RunEventType`. So once M2-T002 adds the mapper entries, the reader
1009+
surfaces every evidence event — independent of whether it was written by a
1010+
spine `emit` (→ consumer) or a direct `audit.record`.
1011+
- Therefore the M6 fold's prerequisite ("evidence events are typed") is met by
1012+
**M2-T002 alone**. Routing each event's *emission* through the spine is a
1013+
separate architectural-purity goal with real cross-component risk (it is what
1014+
produced the double-write).
1015+
1016+
**Decision:** M2 = M2-T001 (done) + M2-T002 (taxonomy/mapper, additive). The
1017+
emit-site migration is deferred to whichever milestone already moves the owning
1018+
component onto the spine (approval/tool-call → M4; skill/route/sandbox/undo →
1019+
M5/M7 inline-eventing removal), each done as an M1-style *replace* with
1020+
exactly-once + count parity. This keeps M2 a ~low-risk additive mapping pass
1021+
and removes the only place the double-write trap could recur.

0 commit comments

Comments
 (0)