feat(kernel): administrative terminal_superseded events — immutable-terminal corrections (#54) - #64
Conversation
…erminal corrections (#54)
There was a problem hiding this comment.
Pull request overview
Adds an administrative terminal_superseded event to the event-log kernel to support immutable-terminal corrections while preserving an audit trail, as promised by the immutable terminal design.
Changes:
- Introduces
terminal_supersededto the event type registry and the event schema enum, with a regression test keeping them in parity. - Extends reducer semantics to allow exactly
terminal_supersededafter a terminal, enforce causation anchoring, and project an oldest-firstsuperseded_history. - Documents the new event type and adds store + reducer tests covering shape validation and replay-domain invariants.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/test_reducer.py |
Adds reducer-domain tests for post-terminal admission, causation anchoring, chaining/history, and G1 reuse on corrections to Succeeded. |
scripts/test_eventstore.py |
Adds EventStore validation and append tests for the new event type, plus schema/type parity enforcement. |
schemas/event.schema.json |
Extends the type enum to include terminal_superseded. |
reference/repo-os-contract.md |
Documents terminal_superseded semantics, scope boundary, and enforcement split (shape vs reducer domain). |
loop/reducer.py |
Implements replay-domain handling for terminal_superseded, including history projection and causation checks. |
loop/events.py |
Registers the new event type and adds write-time payload shape validation for terminal_superseded. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def reduce_events(events: Iterable[Mapping[str, Any]], *, initial: Mapping[str, Any] | None = None) -> dict[str, Any]: | ||
| """Fold events without mutating the supplied stream or initial projection.""" | ||
| state = ({**initial, "runlog_entries": list(initial["runlog_entries"]), "receipts": list(initial["receipts"])} if initial is not None else _empty_projection(None)) | ||
| state = ({**initial, "runlog_entries": list(initial["runlog_entries"]), "receipts": list(initial["receipts"]), "superseded_history": list(initial["superseded_history"])} if initial is not None else _empty_projection(None)) |
|
|
||
|
|
||
| def test_event_types_include_terminal_superseded_and_match_schema_enum() -> None: | ||
| schema = __import__("json").load(open("schemas/event.schema.json", encoding="utf-8")) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f565191172
ℹ️ 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".
|
|
||
|
|
||
| def _validate_superseded_payload_semantics(payload: Mapping[str, Any]) -> None: | ||
| _validate_terminal_payload_semantics(payload) |
There was a problem hiding this comment.
Validate full terminal shape on supersession
For corrections to a non-Succeeded state, this call returns after checking only the canonical state, so a terminal_superseded payload with non-boolean criteria_met, blank evidence entries, or an unsupported completion_policy is accepted and installed as state["terminal"]. That lets administrative corrections create terminal projections that loop.emit.terminate()/loop.contract._validate_terminal() would reject as invalid terminal records; validate the full terminal field shape before the G1-only success checks short-circuit.
Useful? React with 👍 / 👎.
Administrative
terminal_supersededevents for the standalone event-log kernel layer — the immutable-terminal correction mechanism PR #48 and ADR 0001 promised ("record any correction as a separate administrative event").Closes #54
What
terminal_superseded— added toEVENT_TYPES(loop/events.py) and thetypeenum (schemas/event.schema.json), kept in parity by a new regression test.state/criteria_met/evidence/false_completion(+ optionalcompletion_policy) reuseterminal_written's exact key names, plus required non-emptyjustificationandauthority{by, at}(audit label, not authentication).loop/reducer.py):terminal_supersededbefore any terminal is rejected distinctly ("nothing to supersede").causation_idmust equal theevent_idof the currently-effective terminal record — chaining is safe by construction, and citing a stale, already-superseded record is rejected (prevents two independent corrections silently colliding).superseded_historyprojection (oldest-first, withsuperseded_by/superseded_atmarkers); the correction replacesstate["terminal"]. Full audit trail =superseded_history + [terminal].Succeededre-runs the full completion policy, false-completion contradiction, and non-empty-evidence checks — no weaker path for corrections. Direction is unrestricted (revoking a false completion is the flagship use case).## 18section inreference/repo-os-contract.md(zero existing lines edited; reconciles §16's "four writer operations" sentence by cross-reference).loop/emit.py,loop/contract.py,loop/completion.py,loop/fsm.py,schemas/terminal.schema.jsonbyte-unchanged.Verification
2e9e761e): 7/7 argv — extras 688 passed / 15 skipped (baseline 640/15 + 48 new), pyyaml-only 638 / 65 (603 + 35 passing + 13 honestimportorskiprelease-mode skips), targeted reducer+eventstore 80/80,self_eval/frontmatter/py_compile/json.toolclean.causation_id, empty-evidence G1, missing justification, DB append-only triggers on supersession streams).terminal_writtentamper still rejected; stale citation of the original after a chain rejected;criteria_met: {"done": 1}(truthy non-True) rejected; non-monotonic sequence rejected; two-threadexpected_sequenceCAS race — exactly one superseding writer wins; whitespace-onlyauthority.byrejected; resume-across-supersession fold byte-identical to whole-stream fold.Adjudicated design deviation (recorded)
The accepted design's Decision-5 code sketch message ("has no terminal to supersede") contradicted its own AC5 (
match="nothing to supersede"). Adjudication: the AC table is normative — the message is"terminal_superseded has nothing to supersede (no terminal record yet)".Review advisories (non-blocking, recorded)
justification/authoritychecks are preempted by the write-time shape layer (_payload_issuesruns first via_structural_validate_event) — redundancy baked into the design's own Decision 4 + 8 sketches; both layers reject correctly.initial=snapshot (nosuperseded_historykey) raisesKeyError— same pre-existing class asrunlog_entries/receiptsbracket access;reduce_eventshas zero non-test call sites today. Needs a snapshot-versioning story when the event log is wired to real persistence.terminal_writtenfold.Lane: Claudex (GPT-5.6-terra/medium, attempt 2 after an honest attempt-1 stop on the design contradiction). Receipts:
cx_s2_superseded_a1(repair_requested),cx_s2_superseded_a2(accepted).