plan-review: merge answers by finding_id; answer and review in one call; drop the automatic delta

A reviewer's question or objection reached the author, but the author's answer could not
travel back as a normal move: a second review_disposition call REPLACED the wave's whole
answer list (8 of 9 answers were lost on one live wave), answering and re-asking in one
call was refused as PLAN_REVIEW_DISPOSITION_MIXED_ENVELOPE (21 refusals in 7 tasks), items
beside an author finish were refused, and the host bought a delta panel by itself whenever
every blocking finding carried a valid reject (a path that ran 0 times in production).

Now answers MERGE by finding_id across calls (plan_spec.merge_dispositions: a later answer
supersedes only its own id; two entries for one id in ONE call stay contradictory and the
closure table keeps the finding open), both in the engine's closure/exact artifact and in
the durable writer under its lock. An envelope sent beside review_disposition items is
validated FIRST through the read-only prepare seam (an invalid envelope records nothing,
so the answered wave stays current), the answers are recorded merged, and the envelope is
then reviewed with them in view (_apply_disposition(then_review=...)). An author finish or
stop carries its items: they are validated against the critic wave and recorded before
the author source; the kept guard (finish while reviewers run) still refuses and writes
nothing. The automatic earned delta and plan_spec.blocking_fully_rejected are deleted: an
identical envelope without items always replays free, and re-judgement is the mind's
explicit move.

Owner authority: DECISIONS "Communication" item 2 (communication is an option, not an
obligation; reuse surfaces, no if-else); D4 with Q-v = A; "Blocking installs: no skip".
_apply_disposition is split into _disposition_items and _record_disposition so the author
path reuses them. task_results.py stays under its hard line cap (1596/1600).

Tests: tests/test_plan_review_answer_channel.py (merge across calls, supersede-own-id,
same-call duplicate, durable writer, author finish with items, refused finish writes
nothing, unknown id beside a finish, invalid envelope records nothing, changed envelope
with items reviews every slot with the rationale in PRIOR CYCLES); plan_spec units for
merge_dispositions; the MIXED-envelope pins rewritten to the validate-first form; the
earned-delta tests deleted or rewritten as "replay is free until the mind addresses".

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
Ouroboros 2026-09-26 20:35:59 +03:00
parent 91cb19e32a
commit 55b7113b75
13 changed files with 436 additions and 251 deletions

View file

@ -410,7 +410,7 @@ def test_plan_handler_wrapper_preserves_native_meta_for_all_projection_paths(
],
},
},
"PLAN_REVIEW_DISPOSITION_MIXED_ENVELOPE",
"PLAN_RESOURCE_FORM_REQUIRED",
),
(
{"review_disposition": {"review_fingerprint": "", "items": []}},
@ -432,6 +432,7 @@ def test_plan_task_argument_refusals_are_typed_at_the_registry_boundary(
from ouroboros.tools import plan_review
registry = ToolRegistry(repo_dir=tmp_path, drive_root=tmp_path)
registry._ctx.task_id = "t46" # answers beside an envelope are validated against the task's state
monkeypatch.setattr(safety, "check_safety", lambda *_args, **_kwargs: (True, ""))
monkeypatch.setattr(
plan_review,