diff --git a/src/soul_protocol/spec/decisions.py b/src/soul_protocol/spec/decisions.py index 969e78b..5afa3ad 100644 --- a/src/soul_protocol/spec/decisions.py +++ b/src/soul_protocol/spec/decisions.py @@ -1,4 +1,9 @@ # decisions.py — Decision trace payload types and helpers for the Org Journal. +# Updated: feat/rfc07-decision-outcome-attached — extend the +# ``trace_decision_chain`` decision-action filter to include +# ``decision.outcome_attached`` so post-close outcome mutations +# (introduced by RFC 07) surface in the trace alongside the chain +# proper. # Created: feat/decision-traces — Workstream D of the Org Architecture RFC (PR #164). # # Every agent proposal a human edits or rejects becomes a structured, auditable @@ -211,7 +216,15 @@ def trace_decision_chain(journal, correlation_id: UUID) -> list[EventEntry]: the same correlation_id are filtered out. """ events = journal.query(correlation_id=correlation_id, limit=10_000) - decision_actions = {"agent.proposed", "human.corrected", "decision.graduated"} + decision_actions = { + "agent.proposed", + "human.corrected", + "decision.graduated", + # RFC 07: outcome-attachment events update an already-emitted + # Decision's outcome and belong in the chain so traces surface + # the full lifecycle, including post-close mutations. + "decision.outcome_attached", + } chain = [e for e in events if e.action in decision_actions] chain.sort(key=lambda e: e.ts) return chain diff --git a/src/soul_protocol/spec/journal.py b/src/soul_protocol/spec/journal.py index b0aed20..8795117 100644 --- a/src/soul_protocol/spec/journal.py +++ b/src/soul_protocol/spec/journal.py @@ -1,4 +1,10 @@ # journal.py — Org Journal primitives (Actor, DataRef, EventEntry). +# Updated: feat/rfc07-decision-outcome-attached — register +# ``decision.outcome_attached`` in ACTION_NAMESPACES. RFC 07 introduces +# this action as a projection-update event that mutates a Decision's +# outcome after first emit. The hash chain excludes ``outcome``, which +# is what makes the mutation safe; this is the only spec action that +# mutates a prior projection record. # Updated: feat/0.3.2-spike — add EventEntry.seq (backend-assigned, None until # committed). Journal.append now returns the committed EventEntry so callers # can thread seq through idempotency/pagination without racing MAX(seq) or @@ -100,6 +106,12 @@ def _encode_bytes(v: bytes | None) -> str | None: "agent.proposed", "human.corrected", "decision.graduated", + # RFC 07 (Decision Graph Query Layer): projection-update event that + # mutates an already-emitted Decision's ``outcome`` field after the + # chain has closed. The hash chain excludes ``outcome``, so this + # post-hoc update is safe — it is the only event in the spec that + # mutates a prior projection record. + "decision.outcome_attached", # Credentials & Zero-Copy "credential.acquired", "credential.used", diff --git a/tests/test_spec/test_decisions.py b/tests/test_spec/test_decisions.py index 10102fd..3623483 100644 --- a/tests/test_spec/test_decisions.py +++ b/tests/test_spec/test_decisions.py @@ -152,6 +152,10 @@ def test_namespaces_include_decision_actions(): assert "agent.proposed" in ACTION_NAMESPACES assert "human.corrected" in ACTION_NAMESPACES assert "decision.graduated" in ACTION_NAMESPACES + # RFC 07 — projection-update event that mutates an already-emitted + # Decision's outcome field. Registered additively in the journal's + # action catalog so the chain projection can recognize it. + assert "decision.outcome_attached" in ACTION_NAMESPACES def test_build_proposal_event_shape(drafter: Actor):