From 8ef501f3c2c527b2b4347470c8d3f3c18ab2fa2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 01:48:17 +0800 Subject: [PATCH 01/36] feat(orchestration): add scoped repair reviews --- .../contract/stage-review-v1.schema.json | 41 +++- scripts/orchestration/review_runtime.py | 210 +++++++++++++++++- scripts/work-bundle/reviewer_workspace.py | 32 ++- tests/test_wor108_review_frontier.py | 174 +++++++++++++++ 4 files changed, 443 insertions(+), 14 deletions(-) create mode 100644 tests/test_wor108_review_frontier.py diff --git a/references/assets/orchestration/contract/stage-review-v1.schema.json b/references/assets/orchestration/contract/stage-review-v1.schema.json index d7d8155..06cc9b8 100644 --- a/references/assets/orchestration/contract/stage-review-v1.schema.json +++ b/references/assets/orchestration/contract/stage-review-v1.schema.json @@ -7,6 +7,8 @@ "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "gitOid": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, "time": {"type": "string", "format": "date-time", "pattern": "Z$"}, + "reviewMode": {"enum": ["initial", "repair"]}, + "reviewTargetKind": {"enum": ["task", "stage"]}, "targetIdentity": { "type": "object", "additionalProperties": false, @@ -47,8 +49,41 @@ "disposition": {"enum": ["reopen_specification", "repair_plan", "reslice_plan", "repair_task", "repair_oracle", "recover_environment", "record_advisory", "accepted", "rejected"]} } }, + "repairFrontier": { + "type": "object", + "additionalProperties": false, + "required": ["prior_review_id", "blocking_finding_ids", "previous_reviewed_identity", "repaired_identity", "affected_boundaries", "frozen_evidence_reference"], + "properties": { + "prior_review_id": {"$ref": "#/$defs/id"}, + "blocking_finding_ids": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/id"}}, + "previous_reviewed_identity": {"$ref": "#/$defs/targetIdentity"}, + "repaired_identity": {"$ref": "#/$defs/targetIdentity"}, + "affected_boundaries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, + "frozen_evidence_reference": {"$ref": "#/$defs/sha256"} + } + }, + "reviewReset": { + "type": "object", + "additionalProperties": false, + "required": ["prior_review_id", "reason_class", "reason"], + "properties": { + "prior_review_id": {"$ref": "#/$defs/id"}, + "reason_class": {"enum": ["material_redesign", "authority", "scope", "acceptance", "decomposition", "validation_allocation"]}, + "reason": {"type": "string", "minLength": 1} + } + }, "stageReview": { "allOf": [ + { + "if": {"required": ["review_mode"], "properties": {"review_mode": {"const": "repair"}}}, + "then": {"required": ["repair_frontier"], "properties": {"repair_frontier": {"$ref": "#/$defs/repairFrontier"}, "review_reset": {"type": "null"}}}, + "else": {"properties": {"repair_frontier": {"type": "null"}}} + }, + { + "if": {"required": ["review_target_kind"], "properties": {"review_target_kind": {"const": "task"}}}, + "then": {"properties": {"stage": {"const": "implementation"}}}, + "else": {"properties": {"stage": {"enum": ["specification", "plan", "integrated_implementation"]}}} + }, { "if": {"properties": {"verdict": {"const": "accepted"}}}, "then": { @@ -81,11 +116,15 @@ "required": ["review_id", "stage", "target_identity", "reviewer", "evidence", "verdict", "findings", "started_at", "completed_at", "staleness"], "properties": { "review_id": {"$ref": "#/$defs/id"}, + "review_mode": {"$ref": "#/$defs/reviewMode"}, + "review_target_kind": {"$ref": "#/$defs/reviewTargetKind"}, + "repair_frontier": {"anyOf": [{"$ref": "#/$defs/repairFrontier"}, {"type": "null"}]}, + "review_reset": {"anyOf": [{"$ref": "#/$defs/reviewReset"}, {"type": "null"}]}, "reviewer_run": { "type": "object", "additionalProperties": false, "required": ["run_id", "sha256"], "properties": {"run_id": {"$ref": "#/$defs/id"}, "sha256": {"$ref": "#/$defs/sha256"}} }, - "stage": {"enum": ["specification", "plan", "integrated_implementation"]}, + "stage": {"enum": ["specification", "plan", "integrated_implementation", "implementation"]}, "target_identity": {"$ref": "#/$defs/targetIdentity"}, "reviewer": { "type": "object", diff --git a/scripts/orchestration/review_runtime.py b/scripts/orchestration/review_runtime.py index 44c65d1..334aa90 100644 --- a/scripts/orchestration/review_runtime.py +++ b/scripts/orchestration/review_runtime.py @@ -22,7 +22,13 @@ OBLIGATION_BASES = frozenset({"accepted_requirement", "essential_safety", "evidence_integrity", "none"}) TERMINAL_FINDING_DISPOSITIONS = frozenset({"accepted", "rejected"}) STAGE_REVIEW_STAGES = frozenset({"specification", "plan", "integrated_implementation"}) +REVIEW_STAGES = STAGE_REVIEW_STAGES | {"implementation"} REVIEW_VERDICTS = frozenset({"accepted", "repair", "blocked"}) +REVIEW_MODES = frozenset({"initial", "repair"}) +REVIEW_TARGET_KINDS = frozenset({"task", "stage"}) +MATERIAL_CHANGE_CLASSES = frozenset( + {"material_redesign", "authority", "scope", "acceptance", "decomposition", "validation_allocation"} +) PARTICIPATION_FIELDS = ( "authorship", "repair_participation", @@ -58,8 +64,13 @@ TARGET_KEYS = frozenset({"artifact_id", "revision", "sha256", "source_tree"}) EVIDENCE_ITEM_KEYS = frozenset({"kind", "locator", "digest_or_identity", "observation"}) STAGE_REVIEW_KEYS = frozenset( - {"review_id", "stage", "target_identity", "reviewer", "evidence", "verdict", "findings", "started_at", "completed_at", "staleness"} + {"review_id", "review_mode", "review_target_kind", "repair_frontier", "review_reset", "stage", "target_identity", "reviewer", "evidence", "verdict", "findings", "started_at", "completed_at", "staleness"} +) +LEGACY_STAGE_REVIEW_KEYS = STAGE_REVIEW_KEYS - {"review_mode", "review_target_kind", "repair_frontier", "review_reset"} +REPAIR_FRONTIER_KEYS = frozenset( + {"prior_review_id", "blocking_finding_ids", "previous_reviewed_identity", "repaired_identity", "affected_boundaries", "frozen_evidence_reference"} ) +REVIEW_RESET_KEYS = frozenset({"prior_review_id", "reason_class", "reason"}) REVIEWER_KEYS = frozenset( {"agent_id", "capability", *PARTICIPATION_FIELDS, "context_origin"} ) @@ -80,7 +91,7 @@ def reviewer_runtime_root(root: Path) -> Path: def stage_target_identity(root: Path, stage: str, path: Path, *, source_root: Path | None = None) -> dict[str, Any]: - identity = artifact_review_identity(path) if stage == "specification" else plan_review_identity(root, path) + identity = artifact_review_identity(path) if stage in {"specification", "implementation"} else plan_review_identity(root, path) if stage == "integrated_implementation": if source_root is None: raise ReviewContractError("review provenance requires source repository") @@ -109,6 +120,8 @@ def control(path: Path, role: str) -> None: required["control:" + path.relative_to(root).as_posix()] = role control(target, "target") + if stage == "implementation": + return required, missing data, _ = _read_structured(target) members = [target] specifications = [target] if stage == "specification" else [] @@ -237,8 +250,15 @@ def stage_evidence_manifest(root: Path, source_root: Path, context: Mapping[str, if not locator.startswith("control:"): raise ReviewContractError("stage evidence target must be control-local") target = root / locator[8:] - required, missing = stage_evidence_requirements(root, str(context["stage"]), target) - source = source_snapshot_entries(source_root) if context["stage"] == "integrated_implementation" else [] + if context.get("review_mode", "initial") == "repair": + # Repair packets carry the repaired target and a compact immutable + # reference to prior evidence. Full durable history remains lazy. + required, missing = {locator: "target"}, [] + else: + required, missing = stage_evidence_requirements(root, str(context["stage"]), target) + source = (source_snapshot_entries(source_root) + if context["stage"] == "integrated_implementation" and context.get("review_mode", "initial") == "initial" + else []) for entry in source: required.setdefault(entry["locator"], "source_tree") available = {item["locator"]: item for item in artifacts} @@ -259,7 +279,9 @@ def stage_evidence_manifest(root: Path, source_root: Path, context: Mapping[str, entries.append(entry) return {"schema": "stage-evidence-manifest-v1", "stage": context["stage"], "target_identity": context["target_identity"], "entries": entries, - "source_tree": source, "missing": sorted(set(missing))} + "source_tree": source, "missing": sorted(set(missing)), + **({"repair_frontier_reference": context["repair_frontier"]["frozen_evidence_reference"]} + if context.get("review_mode") == "repair" else {})} def validate_stage_evidence(root: Path, context: Mapping[str, Any], packet: Mapping[str, Any]) -> None: @@ -271,9 +293,15 @@ def validate_stage_evidence(root: Path, context: Mapping[str, Any], packet: Mapp target = str(context["target_locator"]) if not target.startswith("control:"): raise ReviewContractError("stage evidence target must be control-local") - required, missing = stage_evidence_requirements(root, str(context["stage"]), root / target[8:]) + if context.get("review_mode", "initial") == "repair": + frontier = _repair_frontier(context.get("repair_frontier")) + if manifest.get("repair_frontier_reference") != frontier["frozen_evidence_reference"]: + raise ReviewContractError("repair stage evidence does not bind frozen evidence") + required, missing = {target: "target"}, [] + else: + required, missing = stage_evidence_requirements(root, str(context["stage"]), root / target[8:]) source = manifest.get("source_tree", []) - if context["stage"] == "integrated_implementation": + if context["stage"] == "integrated_implementation" and context.get("review_mode", "initial") == "initial": if snapshot_tree_identity(source) != context["target_identity"]["source_tree"]: raise ReviewContractError("stage evidence source snapshot is incomplete") for entry in source: @@ -344,6 +372,18 @@ def canonical(value: Any) -> str: result = {key: value for key, value in review.items() if key != "reviewer_run"} context = _mapping(receipt.get("stage_review_context", {}), "reviewer-run provenance context") mode = "direct_source" if review["evidence"]["mode"] == "direct" else review["evidence"]["mode"] + review_context = { + "review_mode": review.get("review_mode", "initial"), + "review_target_kind": review.get("review_target_kind", "stage"), + "repair_frontier": review.get("repair_frontier"), + "review_reset": review.get("review_reset"), + } + receipt_context = { + "review_mode": context.get("review_mode", "initial"), + "review_target_kind": context.get("review_target_kind", "stage"), + "repair_frontier": context.get("repair_frontier"), + "review_reset": context.get("review_reset"), + } if (receipt.get("schema") != "reviewer-process-receipt-v1" or receipt.get("run_id") != run_id or receipt.get("review_id") != review["review_id"] or receipt.get("status") != "passed" or receipt.get("exit_code") != 0 or receipt.get("review_result_sha256") != canonical(result) @@ -352,6 +392,7 @@ def canonical(value: Any) -> str: or context.get("agent_id") != review["reviewer"]["agent_id"] or context.get("evidence_mode") != review["reviewer"]["context_origin"] or context.get("capability") != review["reviewer"]["capability"] or context.get("evidence_mode") != mode + or review_context != receipt_context or receipt.get("isolation") != {"mechanism": "sandbox-exec", "network": "denied", "write_scope": "scratch"} or not context.get("execution_id") or receipt.get("sandbox_profile_sha256") != hashlib.sha256(immutable_file(path.with_suffix(".profile.sb"))).hexdigest() @@ -389,8 +430,10 @@ def _require_current_review(root: Path, stage: str, identity: Mapping[str, Any]) accepted = [] matching_values = [] review_ids: set[str] = set() + historical: dict[str, Mapping[str, Any]] = {} review_root = root / ".work-bundle/orchestration/reviews" try: + documents = [] for path in sorted(review_root.rglob("*")): if path.suffix not in {".json", ".yaml", ".yml"} or not path.is_file(): continue @@ -399,12 +442,33 @@ def _require_current_review(root: Path, stage: str, identity: Mapping[str, Any]) value = _read_document(path) if not isinstance(value, dict) or "review_id" not in value or "stage" not in value: continue + if value["stage"] == stage: + review_key = str(value["review_id"]) + if review_key in historical: + raise ReviewContractError("stage review IDs must be globally unique") + historical[review_key] = value + documents.append(value) + for value in documents: # Old target records remain history, not current acceptance candidates. # In particular, a superseded legacy evidence mode must not poison a # valid replacement review for the actual current artifact. if value["stage"] != stage or value.get("target_identity") != identity: continue record = validate_stage_review(value) + if record.review_mode == "repair": + frontier = record.repair_frontier + assert frontier is not None + prior = historical.get(str(frontier["prior_review_id"])) + if prior is None: + raise ReviewContractError("repair review predecessor is missing") + record = validate_review_sequence(value, previous_review=prior) + elif record.review_reset is not None: + prior = historical.get(str(record.review_reset["prior_review_id"])) + if prior is None: + raise ReviewContractError("initial review reset predecessor is missing") + record = validate_review_sequence( + value, previous_review=prior, material_change=str(record.review_reset["reason_class"]) + ) if record.review_id in review_ids: raise ReviewContractError("stage review IDs must be globally unique") review_ids.add(record.review_id) @@ -490,6 +554,10 @@ class ReviewFindingV1: @dataclass(frozen=True) class StageReviewV1: review_id: str + review_mode: str + review_target_kind: str + repair_frontier: Mapping[str, Any] | None + review_reset: Mapping[str, Any] | None stage: str target_identity: Mapping[str, Any] reviewer: Mapping[str, Any] @@ -564,6 +632,44 @@ def _target_identity(value: Any, name: str = "target_identity") -> Mapping[str, return target +def review_evidence_identity(value: Mapping[str, Any]) -> str: + """Return a compact immutable identity for reusable review evidence.""" + record = _mapping(value, "review") + evidence = _mapping(record.get("evidence"), "evidence") + payload = {"review_id": record.get("review_id"), "evidence": evidence, + "reviewer_run": record.get("reviewer_run")} + return hashlib.sha256( + json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode() + ).hexdigest() + + +def _repair_frontier(value: Any) -> Mapping[str, Any]: + frontier = _mapping(value, "repair_frontier") + _closed(frontier, REPAIR_FRONTIER_KEYS, "repair_frontier") + _identifier(frontier["prior_review_id"], "repair_frontier.prior_review_id") + finding_ids = _string_list(frontier["blocking_finding_ids"], "repair_frontier.blocking_finding_ids") + if not finding_ids or len(finding_ids) != len(set(finding_ids)): + raise ReviewContractError("repair_frontier.blocking_finding_ids must be non-empty and unique") + _target_identity(frontier["previous_reviewed_identity"], "repair_frontier.previous_reviewed_identity") + _target_identity(frontier["repaired_identity"], "repair_frontier.repaired_identity") + boundaries = _string_list(frontier["affected_boundaries"], "repair_frontier.affected_boundaries") + if not boundaries or len(boundaries) != len(set(boundaries)): + raise ReviewContractError("repair_frontier.affected_boundaries must be non-empty and unique") + reference = frontier["frozen_evidence_reference"] + if not isinstance(reference, str) or not SHA256_RE.fullmatch(reference): + raise ReviewContractError("repair_frontier.frozen_evidence_reference must be a lowercase SHA-256") + return frontier + + +def _review_reset(value: Any) -> Mapping[str, Any]: + reset = _mapping(value, "review_reset") + _closed(reset, REVIEW_RESET_KEYS, "review_reset") + _identifier(reset["prior_review_id"], "review_reset.prior_review_id") + _enum(reset["reason_class"], MATERIAL_CHANGE_CLASSES, "review_reset.reason_class") + _nonempty(reset["reason"], "review_reset.reason") + return reset + + def classify_first_broken_owner(finding_class: str) -> tuple[str, str, str]: try: return ROUTES[finding_class] @@ -657,7 +763,10 @@ def validate_stage_review( value: Mapping[str, Any], *, current_target_identity: Mapping[str, Any] | None = None ) -> StageReviewV1: record = _mapping(value, "stage_review_v1") - _closed({key: item for key, item in record.items() if key != "reviewer_run"}, STAGE_REVIEW_KEYS, "stage_review_v1") + envelope = {key: item for key, item in record.items() if key != "reviewer_run"} + keys = set(envelope) + if not LEGACY_STAGE_REVIEW_KEYS.issubset(keys) or not keys.issubset(STAGE_REVIEW_KEYS): + _closed(envelope, STAGE_REVIEW_KEYS, "stage_review_v1") if "reviewer_run" in record: reference = _mapping(record["reviewer_run"], "reviewer_run") _closed(reference, frozenset({"run_id", "sha256"}), "reviewer_run") @@ -665,8 +774,28 @@ def validate_stage_review( if not isinstance(reference["sha256"], str) or not SHA256_RE.fullmatch(reference["sha256"]): raise ReviewContractError("reviewer_run.sha256 must be a lowercase SHA-256") review_id = _identifier(record["review_id"], "review_id") - stage = _enum(record["stage"], STAGE_REVIEW_STAGES, "stage") + review_mode = _enum(record.get("review_mode", "initial"), REVIEW_MODES, "review_mode") + review_target_kind = _enum(record.get("review_target_kind", "stage"), REVIEW_TARGET_KINDS, "review_target_kind") + raw_frontier = record.get("repair_frontier") + raw_reset = record.get("review_reset") + if review_mode == "repair": + if raw_frontier is None: + raise ReviewContractError("repair review requires repair_frontier") + if raw_reset is not None: + raise ReviewContractError("repair review cannot carry review_reset; require a fresh initial review") + repair_frontier = _repair_frontier(raw_frontier) + review_reset = None + else: + if raw_frontier is not None: + raise ReviewContractError("initial review cannot carry repair_frontier") + repair_frontier = None + review_reset = _review_reset(raw_reset) if raw_reset is not None else None + stage = _enum(record["stage"], REVIEW_STAGES, "stage") + if (review_target_kind == "task") != (stage == "implementation"): + raise ReviewContractError("task review target kind requires implementation stage; stage kind requires a WOR-98 stage") target = _target_identity(record["target_identity"]) + if repair_frontier is not None and repair_frontier["repaired_identity"] != target: + raise ReviewContractError("repair frontier repaired identity must equal target_identity") reviewer = _mapping(record["reviewer"], "reviewer") _closed(reviewer, REVIEWER_KEYS, "reviewer") _identifier(reviewer["agent_id"], "reviewer.agent_id") @@ -746,6 +875,10 @@ def validate_stage_review( raise ReviewContractError("snapshot review requires explicit reproducible_snapshot artifacts") return StageReviewV1( review_id, + review_mode, + review_target_kind, + repair_frontier, + review_reset, stage, target, reviewer, @@ -758,6 +891,46 @@ def validate_stage_review( ) +def validate_review_sequence( + value: Mapping[str, Any], *, previous_review: Mapping[str, Any] | None = None, + material_change: str | None = None, +) -> StageReviewV1: + """Bind a re-review to its exact predecessor without replaying history.""" + current = validate_stage_review(value) + if previous_review is None: + if current.review_mode == "repair" or current.review_reset is not None: + raise ReviewContractError("re-review requires the exact previous review") + return current + previous = validate_stage_review(previous_review) + if current.review_mode == "repair": + if material_change is not None: + _enum(material_change, MATERIAL_CHANGE_CLASSES, "material_change") + raise ReviewContractError("material change requires a fresh initial review") + frontier = current.repair_frontier + assert frontier is not None + if previous.verdict != "repair" or frontier["prior_review_id"] != previous.review_id: + raise ReviewContractError("repair frontier must bind the exact prior repair review") + if frontier["previous_reviewed_identity"] != previous.target_identity: + raise ReviewContractError("repair frontier previous reviewed identity does not match prior review") + expected_findings = {item.finding_id for item in previous.findings if item.severity == "blocking"} + if set(frontier["blocking_finding_ids"]) != expected_findings: + raise ReviewContractError("repair frontier blocking finding IDs do not match prior review") + if frontier["frozen_evidence_reference"] != review_evidence_identity(previous_review): + raise ReviewContractError("repair frontier frozen evidence reference does not match prior review") + return current + if material_change is not None: + _enum(material_change, MATERIAL_CHANGE_CLASSES, "material_change") + reset = current.review_reset + if (reset is None or reset["prior_review_id"] != previous.review_id + or reset["reason_class"] != material_change): + raise ReviewContractError("material change requires a recorded fresh initial review reset") + if current.reviewer["agent_id"] == previous.reviewer["agent_id"] or current.reviewer["capability"] != "judgment": + raise ReviewContractError("reset requires a fresh capable independent reviewer identity") + elif current.review_reset is not None: + raise ReviewContractError("review_reset requires a classified material change") + return current + + def validate_stage_reviews( values: Sequence[Mapping[str, Any]], *, current_target_identities: Mapping[str, Mapping[str, Any]] | None = None, @@ -768,8 +941,25 @@ def validate_stage_reviews( ids = [record.review_id for record in records] if len(ids) != len(set(ids)): raise ReviewContractError("stage review IDs must be globally unique") + raw_by_id = {record.review_id: value for record, value in zip(records, values)} + sequenced = [] + for record, value in zip(records, values): + if record.review_mode == "repair": + assert record.repair_frontier is not None + prior = raw_by_id.get(str(record.repair_frontier["prior_review_id"])) + if prior is None: + raise ReviewContractError("repair review predecessor is missing") + record = validate_review_sequence(value, previous_review=prior) + elif record.review_reset is not None: + prior = raw_by_id.get(str(record.review_reset["prior_review_id"])) + if prior is None: + raise ReviewContractError("initial review reset predecessor is missing") + record = validate_review_sequence( + value, previous_review=prior, material_change=str(record.review_reset["reason_class"]) + ) + sequenced.append(record) countable: dict[str, StageReviewV1] = {} - for record in sorted(records, key=lambda item: item.completed_at): + for record in sorted((item for item in sequenced if item.review_target_kind == "stage"), key=lambda item: item.completed_at): current = _target_identity(current_target_identities[record.stage], "current_target_identity") if record.target_identity == current: countable.pop(record.stage, None) diff --git a/scripts/work-bundle/reviewer_workspace.py b/scripts/work-bundle/reviewer_workspace.py index 00a5d11..92698d1 100644 --- a/scripts/work-bundle/reviewer_workspace.py +++ b/scripts/work-bundle/reviewer_workspace.py @@ -47,14 +47,27 @@ def _review_runtime(): def _validate_stage_context(context: object) -> dict[str, object]: fields = {"stage", "target_identity", "target_locator", "agent_id", "capability", "execution_id", "evidence_mode"} - if not isinstance(context, dict) or set(context) != fields: + repair_fields = {"review_mode", "review_target_kind", "repair_frontier", "review_reset"} + if not isinstance(context, dict) or frozenset(context) not in {frozenset(fields), frozenset(fields | repair_fields)}: raise ReviewerWorkspaceError("WB_REVIEW_STAGE_CONTEXT_INVALID") - if (context["stage"] not in {"specification", "plan", "integrated_implementation"} + if (context["stage"] not in {"specification", "plan", "integrated_implementation", "implementation"} or context["capability"] not in {"standard", "judgment"} or context["evidence_mode"] not in {"direct_source", "reproducible_snapshot", "packet_only"} or not all(isinstance(context[key], str) and context[key] for key in ("agent_id", "execution_id"))): raise ReviewerWorkspaceError("WB_REVIEW_STAGE_CONTEXT_INVALID") _review_runtime()._target_identity(context["target_identity"]) + if repair_fields.issubset(context): + try: + mode = _review_runtime()._enum(context["review_mode"], _review_runtime().REVIEW_MODES, "review_mode") + _review_runtime()._enum(context["review_target_kind"], _review_runtime().REVIEW_TARGET_KINDS, "review_target_kind") + if mode == "repair": + _review_runtime()._repair_frontier(context["repair_frontier"]) + if context["review_reset"] is not None: + raise ValueError("repair reset") + elif context["repair_frontier"] is not None: + raise ValueError("initial frontier") + except (ValueError, TypeError): + raise ReviewerWorkspaceError("WB_REVIEW_STAGE_CONTEXT_INVALID") from None return context @@ -668,11 +681,24 @@ def run_sandboxed_reviewer(workspace: Path, argv: list[str]) -> dict[str, object except (ValueError, TypeError) as error: raise ReviewerWorkspaceError("WB_REVIEW_STAGE_OUTPUT_INVALID") from error mode = "direct_source" if validated.evidence["mode"] == "direct" else validated.evidence["mode"] + review_context = { + "review_mode": validated.review_mode, + "review_target_kind": validated.review_target_kind, + "repair_frontier": validated.repair_frontier, + "review_reset": validated.review_reset, + } + packet_context = { + "review_mode": context.get("review_mode", "initial"), + "review_target_kind": context.get("review_target_kind", "stage"), + "repair_frontier": context.get("repair_frontier"), + "review_reset": context.get("review_reset"), + } if ("reviewer_run" in review or validated.review_id != review_id or validated.stage != context["stage"] or validated.target_identity != context["target_identity"] or validated.reviewer["agent_id"] != context["agent_id"] or validated.reviewer["context_origin"] != context["evidence_mode"] - or validated.reviewer["capability"] != context["capability"] or mode != context["evidence_mode"]): + or validated.reviewer["capability"] != context["capability"] or mode != context["evidence_mode"] + or review_context != packet_context): raise ReviewerWorkspaceError("WB_REVIEW_STAGE_OUTPUT_MISMATCH") if validated.verdict == "accepted": try: diff --git a/tests/test_wor108_review_frontier.py b/tests/test_wor108_review_frontier.py new file mode 100644 index 0000000..4371a1d --- /dev/null +++ b/tests/test_wor108_review_frontier.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +import hashlib +import json +import sys +from copy import deepcopy +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts" / "orchestration")) +sys.path.insert(0, str(REPO_ROOT / "scripts" / "work-bundle")) + +import review_runtime # noqa: E402 +import reviewer_workspace # noqa: E402 + + +ZERO_SHA = "0" * 64 + + +def identity(name: str, digest: str) -> dict[str, object]: + return {"artifact_id": name, "revision": "1", "sha256": digest, "source_tree": None} + + +def finding(target: dict[str, object], finding_id: str = "RF-FINDING-1") -> dict[str, object]: + return { + "finding_id": finding_id, + "stage": "implementation", + "class": "implementation_defect", + "severity": "blocking", + "first_broken_artifact": "implementation", + "obligation_basis": "accepted_requirement", + "evidence": [{"kind": "test", "locator": "RF", "digest_or_identity": "RF-RED", "observation": "failed"}], + "target_identity": target, + "summary": "Repair this bounded defect.", + "recommended_owner": "task_owner", + "disposition": "repair_task", + } + + +def review(*, target: dict[str, object], mode: str = "initial", kind: str = "task", agent: str = "reviewer-new") -> dict[str, object]: + return { + "review_id": f"review-{mode}-{agent}", + "review_mode": mode, + "review_target_kind": kind, + "repair_frontier": None, + "review_reset": None, + "stage": "implementation" if kind == "task" else "plan", + "target_identity": target, + "reviewer": { + "agent_id": agent, + "capability": "judgment", + "authorship": "none", + "repair_participation": "none", + "decision_participation": "none", + "deliberation_participation": "none", + "context_origin": "direct_source", + }, + "evidence": {"mode": "direct", "capabilities": ["bounded source inspection"], "unavailable_evidence": [], "commands": [], "artifacts": []}, + "verdict": "accepted", + "findings": [], + "started_at": "2026-09-06T00:00:00Z", + "completed_at": "2026-09-06T00:01:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + + +def repair_pair() -> tuple[dict[str, object], dict[str, object]]: + old = identity("task-rf", "1" * 64) + new = identity("task-rf", "2" * 64) + prior = review(target=old, agent="reviewer-prior") + prior["review_id"] = "review-prior" + prior["verdict"] = "repair" + prior["findings"] = [finding(old)] + current = review(target=new, mode="repair") + current["repair_frontier"] = { + "prior_review_id": prior["review_id"], + "blocking_finding_ids": ["RF-FINDING-1"], + "previous_reviewed_identity": old, + "repaired_identity": new, + "affected_boundaries": ["scripts/orchestration/review_runtime.py:validate_stage_review"], + "frozen_evidence_reference": review_runtime.review_evidence_identity(prior), + } + return prior, current + + +def test_rf_01_initial_and_repair_modes_are_native_and_closed() -> None: + initial = review(target=identity("task-rf", ZERO_SHA)) + validated = review_runtime.validate_stage_review(initial) + assert (validated.review_mode, validated.review_target_kind, validated.repair_frontier) == ("initial", "task", None) + schema = json.loads((REPO_ROOT / "references/assets/orchestration/contract/stage-review-v1.schema.json").read_text()) + assert schema["$defs"]["reviewMode"]["enum"] == ["initial", "repair"] + assert schema["$defs"]["reviewTargetKind"]["enum"] == ["task", "stage"] + initial["repair_frontier"] = {"unexpected": True} + with pytest.raises(review_runtime.ReviewContractError, match="initial review cannot carry"): + review_runtime.validate_stage_review(initial) + + +def test_rf_02_repair_frontier_binds_prior_findings_evidence_and_exact_identities() -> None: + prior, current = repair_pair() + validated = review_runtime.validate_review_sequence(current, previous_review=prior) + assert validated.repair_frontier["previous_reviewed_identity"] == prior["target_identity"] + schema = json.loads((REPO_ROOT / "references/assets/orchestration/contract/stage-review-v1.schema.json").read_text()) + assert set(schema["$defs"]["repairFrontier"]["required"]) == set(current["repair_frontier"]) + tampered = deepcopy(current) + tampered["repair_frontier"]["blocking_finding_ids"] = ["UNKNOWN"] + with pytest.raises(review_runtime.ReviewContractError, match="blocking finding IDs"): + review_runtime.validate_review_sequence(tampered, previous_review=prior) + + +def test_rf_03_repair_reuses_frozen_evidence_without_copying_history() -> None: + prior, current = repair_pair() + prior["evidence"]["artifacts"] = [{"path": f"history/{index}.json", "sha256": ZERO_SHA} for index in range(100)] + current["repair_frontier"]["frozen_evidence_reference"] = review_runtime.review_evidence_identity(prior) + validated = review_runtime.validate_review_sequence(current, previous_review=prior) + assert validated.evidence["artifacts"] == [] + assert len(json.dumps(validated.repair_frontier)) < len(json.dumps(prior["evidence"])) + + +@pytest.mark.parametrize("reason_class", ["material_redesign", "authority", "scope", "acceptance", "decomposition", "validation_allocation"]) +def test_rf_04_material_change_requires_fresh_initial_review(reason_class: str) -> None: + prior, repair = repair_pair() + with pytest.raises(review_runtime.ReviewContractError, match="fresh initial review"): + review_runtime.validate_review_sequence(repair, previous_review=prior, material_change=reason_class) + reset = review(target=repair["target_identity"], agent="reviewer-reset") + reset["review_reset"] = {"prior_review_id": prior["review_id"], "reason_class": reason_class, "reason": "accepted boundary changed"} + assert review_runtime.validate_review_sequence(reset, previous_review=prior, material_change=reason_class).review_mode == "initial" + + +def test_rf_05_reset_rejects_reused_repair_reviewer_identity() -> None: + prior, repair = repair_pair() + reset = review(target=repair["target_identity"], agent=prior["reviewer"]["agent_id"]) + reset["review_reset"] = {"prior_review_id": prior["review_id"], "reason_class": "scope", "reason": "scope changed"} + with pytest.raises(review_runtime.ReviewContractError, match="fresh capable independent reviewer"): + review_runtime.validate_review_sequence(reset, previous_review=prior, material_change="scope") + + +def test_rf_06_repair_rejects_stale_or_relabelled_identity() -> None: + prior, current = repair_pair() + current["target_identity"] = identity("task-rf", "3" * 64) + with pytest.raises(review_runtime.ReviewContractError, match="repaired identity"): + review_runtime.validate_review_sequence(current, previous_review=prior) + + +def test_rf_07_repair_still_detects_regression_in_affected_boundary() -> None: + prior, current = repair_pair() + current["findings"] = [finding(current["target_identity"], "RF-REGRESSION")] + with pytest.raises(review_runtime.ReviewContractError, match="accepted review cannot contain blocking"): + review_runtime.validate_review_sequence(current, previous_review=prior) + + +def test_rf_08_repair_packet_is_bounded_for_task_and_stage_targets(tmp_path: Path) -> None: + prior, current = repair_pair() + target = tmp_path / "target.md" + target.write_text("---\nid: task-rf\n---\nrepaired\n") + current["target_identity"] = review_runtime.artifact_review_identity(target) + current["repair_frontier"]["repaired_identity"] = current["target_identity"] + for kind in ("task", "stage"): + current["review_target_kind"] = kind + current["stage"] = "implementation" if kind == "task" else "plan" + context = { + "stage": current["stage"], "target_identity": current["target_identity"], "target_locator": "control:target.md", + "agent_id": "reviewer-new", "capability": "judgment", "execution_id": "exec-rf", + "evidence_mode": "reproducible_snapshot", "review_mode": "repair", "review_target_kind": kind, + "repair_frontier": current["repair_frontier"], "review_reset": None, + } + artifact = {"locator": "control:target.md", "sha256": hashlib.sha256(target.read_bytes()).hexdigest(), "content_base64": ""} + manifest = review_runtime.stage_evidence_manifest(tmp_path, tmp_path, context, [artifact]) + assert manifest["missing"] == [] + assert [entry["locator"] for entry in manifest["entries"]] == ["control:target.md"] + assert manifest["repair_frontier_reference"] == current["repair_frontier"]["frozen_evidence_reference"] + assert reviewer_workspace._validate_stage_context(context)["review_target_kind"] == kind From 5265c96f0932be5ce20f1996f6e6889a3407d71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 02:04:03 +0800 Subject: [PATCH 02/36] fix(orchestration): bind task repair acceptance --- .../contract/stage-review-v1.schema.json | 7 +- .../assets/orchestration/contract/task-v1.md | 3 + scripts/orchestration/execution_context.py | 15 ++++ scripts/orchestration/review_runtime.py | 62 ++++++++++++++-- scripts/work-bundle/reviewer_workspace.py | 2 +- tests/test_orchestration_execution_context.py | 71 +++++++++++++++++++ tests/test_wor108_review_frontier.py | 39 +++++----- 7 files changed, 169 insertions(+), 30 deletions(-) diff --git a/references/assets/orchestration/contract/stage-review-v1.schema.json b/references/assets/orchestration/contract/stage-review-v1.schema.json index 06cc9b8..d7139c2 100644 --- a/references/assets/orchestration/contract/stage-review-v1.schema.json +++ b/references/assets/orchestration/contract/stage-review-v1.schema.json @@ -80,9 +80,8 @@ "else": {"properties": {"repair_frontier": {"type": "null"}}} }, { - "if": {"required": ["review_target_kind"], "properties": {"review_target_kind": {"const": "task"}}}, - "then": {"properties": {"stage": {"const": "implementation"}}}, - "else": {"properties": {"stage": {"enum": ["specification", "plan", "integrated_implementation"]}}} + "if": {"required": ["review_target_kind"]}, + "then": {"properties": {"review_target_kind": {"const": "stage"}}} }, { "if": {"properties": {"verdict": {"const": "accepted"}}}, @@ -124,7 +123,7 @@ "type": "object", "additionalProperties": false, "required": ["run_id", "sha256"], "properties": {"run_id": {"$ref": "#/$defs/id"}, "sha256": {"$ref": "#/$defs/sha256"}} }, - "stage": {"enum": ["specification", "plan", "integrated_implementation", "implementation"]}, + "stage": {"enum": ["specification", "plan", "integrated_implementation"]}, "target_identity": {"$ref": "#/$defs/targetIdentity"}, "reviewer": { "type": "object", diff --git a/references/assets/orchestration/contract/task-v1.md b/references/assets/orchestration/contract/task-v1.md index 06e17ba..9f64355 100644 --- a/references/assets/orchestration/contract/task-v1.md +++ b/references/assets/orchestration/contract/task-v1.md @@ -146,6 +146,9 @@ A legacy 3-column `Command or inspection | Proves | Expected` row without YAML ` - Shared completion validation has passed: task/plan identity, executor-result shape, fresh required validation, `knowledge_disposition`, and unresolved/blocker state. - When `acceptance_review.required` is false or omitted, `Completed` does not require an independent reviewer or `accept`. - When `acceptance_review.required` is true, `acceptance_review.verdict` is `accept`. +- A newly authored task acceptance record exposes `review_mode: initial|repair` and `review_target_kind: task`. Legacy records without these fields are tolerated only as initial-review migration input. +- When `task_fit_check.result` is `repaired`, completion requires `review_mode: repair`, the native review envelope fields, and exactly one `previous_review`. The closed `repair_frontier` binds that predecessor's review ID, blocking finding IDs, previous and repaired target identities, affected boundaries, and frozen evidence identity. Whole review history is not embedded or reacquired. +- Material redesign or changed authority, scope, acceptance, decomposition, or validation allocation uses a fresh `initial` review with `review_reset`; it may not reuse the repair reviewer identity. ## Planning verification diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index beca22a..9041c38 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1743,6 +1743,21 @@ def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, raise SystemExit("Executor result acceptance_review.required must match compiled review_required") if compiled_required and state == "completed" and review.get("verdict") != "accept": raise SystemExit("Review-required task cannot complete without acceptance_review.verdict: accept") + if not compiled_required or state != "completed": + return + fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} + repaired = fit.get("result") == "repaired" + mode = review.get("review_mode") + if repaired and mode != "repair": + raise SystemExit("Repaired task completion requires a sequenced repair acceptance_review") + if mode is not None: + try: + from review_runtime import ReviewContractError, validate_task_acceptance_review + validated = validate_task_acceptance_review(review) + except (ReviewContractError, TypeError, ValueError) as error: + raise SystemExit(f"Task acceptance review is invalid: {error}") from error + if validated.verdict != "accepted": + raise SystemExit("Review-required task cannot complete without an accepted task review") def _yaml_scalar(value: Any) -> str: diff --git a/scripts/orchestration/review_runtime.py b/scripts/orchestration/review_runtime.py index 334aa90..dc3bfc0 100644 --- a/scripts/orchestration/review_runtime.py +++ b/scripts/orchestration/review_runtime.py @@ -22,7 +22,6 @@ OBLIGATION_BASES = frozenset({"accepted_requirement", "essential_safety", "evidence_integrity", "none"}) TERMINAL_FINDING_DISPOSITIONS = frozenset({"accepted", "rejected"}) STAGE_REVIEW_STAGES = frozenset({"specification", "plan", "integrated_implementation"}) -REVIEW_STAGES = STAGE_REVIEW_STAGES | {"implementation"} REVIEW_VERDICTS = frozenset({"accepted", "repair", "blocked"}) REVIEW_MODES = frozenset({"initial", "repair"}) REVIEW_TARGET_KINDS = frozenset({"task", "stage"}) @@ -91,7 +90,7 @@ def reviewer_runtime_root(root: Path) -> Path: def stage_target_identity(root: Path, stage: str, path: Path, *, source_root: Path | None = None) -> dict[str, Any]: - identity = artifact_review_identity(path) if stage in {"specification", "implementation"} else plan_review_identity(root, path) + identity = artifact_review_identity(path) if stage == "specification" else plan_review_identity(root, path) if stage == "integrated_implementation": if source_root is None: raise ReviewContractError("review provenance requires source repository") @@ -120,8 +119,6 @@ def control(path: Path, role: str) -> None: required["control:" + path.relative_to(root).as_posix()] = role control(target, "target") - if stage == "implementation": - return required, missing data, _ = _read_structured(target) members = [target] specifications = [target] if stage == "specification" else [] @@ -790,9 +787,9 @@ def validate_stage_review( raise ReviewContractError("initial review cannot carry repair_frontier") repair_frontier = None review_reset = _review_reset(raw_reset) if raw_reset is not None else None - stage = _enum(record["stage"], REVIEW_STAGES, "stage") - if (review_target_kind == "task") != (stage == "implementation"): - raise ReviewContractError("task review target kind requires implementation stage; stage kind requires a WOR-98 stage") + stage = _enum(record["stage"], STAGE_REVIEW_STAGES, "stage") + if review_target_kind != "stage": + raise ReviewContractError("stage_review_v1 review_target_kind must be stage") target = _target_identity(record["target_identity"]) if repair_frontier is not None and repair_frontier["repaired_identity"] != target: raise ReviewContractError("repair frontier repaired identity must equal target_identity") @@ -931,6 +928,57 @@ def validate_review_sequence( return current +def _task_review_as_stage(value: Mapping[str, Any]) -> dict[str, Any]: + """Adapt the existing task acceptance record to the native review validator.""" + record = _mapping(value, "task acceptance_review") + verdict = {"accept": "accepted", "repair": "repair", "blocked": "blocked"}.get(record.get("verdict")) + if verdict is None: + raise ReviewContractError("task acceptance_review verdict must be accept, repair, or blocked") + return { + "review_id": record.get("review_id"), + "review_mode": record.get("review_mode"), + "review_target_kind": "stage", + "repair_frontier": record.get("repair_frontier"), + "review_reset": record.get("review_reset"), + # This is an adapter discriminator, not a fourth stage-review seat. + "stage": "plan", + "target_identity": record.get("target_identity"), + "reviewer": record.get("reviewer"), + "evidence": record.get("evidence"), + "verdict": verdict, + "findings": record.get("findings"), + "started_at": record.get("started_at"), + "completed_at": record.get("completed_at"), + "staleness": record.get("staleness"), + **({"reviewer_run": record["reviewer_run"]} if "reviewer_run" in record else {}), + } + + +def validate_task_acceptance_review(value: Mapping[str, Any]) -> StageReviewV1: + """Validate a task acceptance review and its one bounded predecessor.""" + record = _mapping(value, "task acceptance_review") + if (record.get("required") is not True or record.get("review_target_kind") != "task" + or record.get("reviewer_independent") is not True): + raise ReviewContractError("task acceptance_review requires review_target_kind: task") + mode = _enum(record.get("review_mode"), REVIEW_MODES, "task acceptance_review.review_mode") + current = _task_review_as_stage(record) + previous = record.get("previous_review") + if mode == "repair": + if not isinstance(previous, Mapping): + raise ReviewContractError("task repair review requires its exact previous_review") + if "previous_review" in previous: + raise ReviewContractError("task repair review may carry exactly one previous_review; older history stays lazy") + return validate_review_sequence(current, previous_review=_task_review_as_stage(previous)) + if record.get("review_reset") is not None: + if not isinstance(previous, Mapping): + raise ReviewContractError("task initial reset requires its exact previous_review") + reset = _mapping(record["review_reset"], "review_reset") + return validate_review_sequence( + current, previous_review=_task_review_as_stage(previous), material_change=str(reset.get("reason_class")) + ) + return validate_review_sequence(current) + + def validate_stage_reviews( values: Sequence[Mapping[str, Any]], *, current_target_identities: Mapping[str, Mapping[str, Any]] | None = None, diff --git a/scripts/work-bundle/reviewer_workspace.py b/scripts/work-bundle/reviewer_workspace.py index 92698d1..a5149ca 100644 --- a/scripts/work-bundle/reviewer_workspace.py +++ b/scripts/work-bundle/reviewer_workspace.py @@ -50,7 +50,7 @@ def _validate_stage_context(context: object) -> dict[str, object]: repair_fields = {"review_mode", "review_target_kind", "repair_frontier", "review_reset"} if not isinstance(context, dict) or frozenset(context) not in {frozenset(fields), frozenset(fields | repair_fields)}: raise ReviewerWorkspaceError("WB_REVIEW_STAGE_CONTEXT_INVALID") - if (context["stage"] not in {"specification", "plan", "integrated_implementation", "implementation"} + if (context["stage"] not in {"specification", "plan", "integrated_implementation"} or context["capability"] not in {"standard", "judgment"} or context["evidence_mode"] not in {"direct_source", "reproducible_snapshot", "packet_only"} or not all(isinstance(context[key], str) and context[key] for key in ("agent_id", "execution_id"))): diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index ed78dde..3a97197 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -2381,6 +2381,77 @@ def test_validate_executor_result_rejects_review_required_upgrade(tmp_path: Path _validate_observed(_read_handoff(handoff), brief) +def _repair_completion_fixture() -> tuple[dict, dict]: + brief = { + "task_id": "task-rf", "plan_id": "plan-rf", "review_required": True, + "source_ids": [], "files": {"read": [], "write": [], "forbidden": []}, + "truth_basis": {}, "validation": [], + "evidence_applicability": { + "metadata": {"required": False, "reasons": []}, + "repository": {"required": False, "reasons": []}, + "codegraph": {"required": False, "reasons": []}, + }, + "evidence_capability": {"result": "no_validation_bearing_obligation", "invariants": []}, + } + handoff = { + "type": "executor-result", "related": {"plan": "plan-rf", "task": "task-rf"}, + "result": {"state": "completed"}, + "task_fit_check": {"task": "task-rf", "result": "repaired"}, + "acceptance_review": {"required": True, "verdict": "accept"}, + "knowledge_disposition": {"action": "none", "reason": "No authority change.", "affected_authority": []}, + } + return brief, handoff + + +def test_rf_task_repair_completion_rejects_unsequenced_acceptance_review() -> None: + brief, handoff = _repair_completion_fixture() + with pytest.raises(SystemExit, match="repair.*review|review.*repair"): + execution_context.validate_executor_result_for_task(handoff, brief) + + +def test_rf_task_repair_completion_rejects_stale_repair_frontier() -> None: + brief, handoff = _repair_completion_fixture() + old = {"artifact_id": "task-rf", "revision": "1", "sha256": "1" * 64, "source_tree": None} + new = {"artifact_id": "task-rf", "revision": "1", "sha256": "2" * 64, "source_tree": None} + prior = { + "required": True, "reviewer_independent": True, "verdict": "repair", "review_id": "review-prior", + "review_mode": "initial", "review_target_kind": "task", "repair_frontier": None, "review_reset": None, + "target_identity": old, + "reviewer": {"agent_id": "reviewer-prior", "capability": "judgment", "authorship": "none", "repair_participation": "none", "decision_participation": "none", "deliberation_participation": "none", "context_origin": "direct_source"}, + "evidence": {"mode": "direct", "capabilities": ["source inspection"], "unavailable_evidence": [], "commands": [], "artifacts": []}, + "findings": [{ + "finding_id": "RF-FINDING-1", "stage": "implementation", "class": "implementation_defect", "severity": "blocking", + "first_broken_artifact": "implementation", "obligation_basis": "accepted_requirement", + "evidence": [{"kind": "test", "locator": "RF", "digest_or_identity": "RF-RED", "observation": "failed"}], + "target_identity": old, "summary": "repair", "recommended_owner": "task_owner", "disposition": "repair_task", + }], + "started_at": "2026-09-06T00:00:00Z", "completed_at": "2026-09-06T00:01:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + from review_runtime import review_evidence_identity + handoff["acceptance_review"] = { + "required": True, "reviewer_independent": True, "verdict": "accept", "review_id": "review-repair", + "review_mode": "repair", "review_target_kind": "task", "review_reset": None, + "repair_frontier": { + "prior_review_id": "review-prior", "blocking_finding_ids": ["RF-FINDING-1"], + "previous_reviewed_identity": old, "repaired_identity": new, + "affected_boundaries": ["scripts/orchestration/execution_context.py:_assert_handoff_review_matches_task"], + "frozen_evidence_reference": review_evidence_identity(prior), + }, + "target_identity": new, + "reviewer": {**prior["reviewer"], "agent_id": "reviewer-new"}, + "evidence": {"mode": "direct", "capabilities": ["bounded source inspection"], "unavailable_evidence": [], "commands": [], "artifacts": []}, + "findings": [], "previous_review": prior, + "started_at": "2026-09-06T00:02:00Z", "completed_at": "2026-09-06T00:03:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + assert execution_context.validate_executor_result_for_task(handoff, brief)["result_state"] == "completed" + stale = deepcopy(handoff) + stale["acceptance_review"]["target_identity"] = {**new, "sha256": "3" * 64} + with pytest.raises(SystemExit, match="repaired identity|frontier"): + execution_context.validate_executor_result_for_task(stale, brief) + + def test_no_review_update_stays_closure_eligible_when_handoff_self_upgrades() -> None: handoffs = [ { diff --git a/tests/test_wor108_review_frontier.py b/tests/test_wor108_review_frontier.py index 4371a1d..bfa5aeb 100644 --- a/tests/test_wor108_review_frontier.py +++ b/tests/test_wor108_review_frontier.py @@ -40,14 +40,14 @@ def finding(target: dict[str, object], finding_id: str = "RF-FINDING-1") -> dict } -def review(*, target: dict[str, object], mode: str = "initial", kind: str = "task", agent: str = "reviewer-new") -> dict[str, object]: +def review(*, target: dict[str, object], mode: str = "initial", kind: str = "stage", agent: str = "reviewer-new") -> dict[str, object]: return { "review_id": f"review-{mode}-{agent}", "review_mode": mode, "review_target_kind": kind, "repair_frontier": None, "review_reset": None, - "stage": "implementation" if kind == "task" else "plan", + "stage": "plan", "target_identity": target, "reviewer": { "agent_id": agent, @@ -89,7 +89,7 @@ def repair_pair() -> tuple[dict[str, object], dict[str, object]]: def test_rf_01_initial_and_repair_modes_are_native_and_closed() -> None: initial = review(target=identity("task-rf", ZERO_SHA)) validated = review_runtime.validate_stage_review(initial) - assert (validated.review_mode, validated.review_target_kind, validated.repair_frontier) == ("initial", "task", None) + assert (validated.review_mode, validated.review_target_kind, validated.repair_frontier) == ("initial", "stage", None) schema = json.loads((REPO_ROOT / "references/assets/orchestration/contract/stage-review-v1.schema.json").read_text()) assert schema["$defs"]["reviewMode"]["enum"] == ["initial", "repair"] assert schema["$defs"]["reviewTargetKind"]["enum"] == ["task", "stage"] @@ -157,18 +157,21 @@ def test_rf_08_repair_packet_is_bounded_for_task_and_stage_targets(tmp_path: Pat target.write_text("---\nid: task-rf\n---\nrepaired\n") current["target_identity"] = review_runtime.artifact_review_identity(target) current["repair_frontier"]["repaired_identity"] = current["target_identity"] - for kind in ("task", "stage"): - current["review_target_kind"] = kind - current["stage"] = "implementation" if kind == "task" else "plan" - context = { - "stage": current["stage"], "target_identity": current["target_identity"], "target_locator": "control:target.md", - "agent_id": "reviewer-new", "capability": "judgment", "execution_id": "exec-rf", - "evidence_mode": "reproducible_snapshot", "review_mode": "repair", "review_target_kind": kind, - "repair_frontier": current["repair_frontier"], "review_reset": None, - } - artifact = {"locator": "control:target.md", "sha256": hashlib.sha256(target.read_bytes()).hexdigest(), "content_base64": ""} - manifest = review_runtime.stage_evidence_manifest(tmp_path, tmp_path, context, [artifact]) - assert manifest["missing"] == [] - assert [entry["locator"] for entry in manifest["entries"]] == ["control:target.md"] - assert manifest["repair_frontier_reference"] == current["repair_frontier"]["frozen_evidence_reference"] - assert reviewer_workspace._validate_stage_context(context)["review_target_kind"] == kind + context = { + "stage": "plan", "target_identity": current["target_identity"], "target_locator": "control:target.md", + "agent_id": "reviewer-new", "capability": "judgment", "execution_id": "exec-rf", + "evidence_mode": "reproducible_snapshot", "review_mode": "repair", "review_target_kind": "stage", + "repair_frontier": current["repair_frontier"], "review_reset": None, + } + artifact = {"locator": "control:target.md", "sha256": hashlib.sha256(target.read_bytes()).hexdigest(), "content_base64": ""} + manifest = review_runtime.stage_evidence_manifest(tmp_path, tmp_path, context, [artifact]) + assert manifest["missing"] == [] + assert [entry["locator"] for entry in manifest["entries"]] == ["control:target.md"] + assert manifest["repair_frontier_reference"] == current["repair_frontier"]["frozen_evidence_reference"] + assert reviewer_workspace._validate_stage_context(context)["review_target_kind"] == "stage" + + task_prior = {key: value for key, value in prior.items() if key != "stage"} + task_prior.update(required=True, reviewer_independent=True, review_target_kind="task") + task_current = {key: value for key, value in current.items() if key != "stage"} + task_current.update(required=True, reviewer_independent=True, verdict="accept", review_target_kind="task", previous_review=task_prior) + assert review_runtime.validate_task_acceptance_review(task_current).target_identity == current["target_identity"] From 09982b159e431242d3f863f7bea273be7cfc717d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 02:11:09 +0800 Subject: [PATCH 03/36] fix(orchestration): verify task repair provenance --- .../assets/orchestration/contract/task-v1.md | 1 + scripts/orchestration/execution_context.py | 17 ++++++++++ scripts/orchestration/review_runtime.py | 27 +++++++++++----- tests/test_orchestration_execution_context.py | 32 +++++++++++++++++-- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/references/assets/orchestration/contract/task-v1.md b/references/assets/orchestration/contract/task-v1.md index 9f64355..19e5d37 100644 --- a/references/assets/orchestration/contract/task-v1.md +++ b/references/assets/orchestration/contract/task-v1.md @@ -148,6 +148,7 @@ A legacy 3-column `Command or inspection | Proves | Expected` row without YAML ` - When `acceptance_review.required` is true, `acceptance_review.verdict` is `accept`. - A newly authored task acceptance record exposes `review_mode: initial|repair` and `review_target_kind: task`. Legacy records without these fields are tolerated only as initial-review migration input. - When `task_fit_check.result` is `repaired`, completion requires `review_mode: repair`, the native review envelope fields, and exactly one `previous_review`. The closed `repair_frontier` binds that predecessor's review ID, blocking finding IDs, previous and repaired target identities, affected boundaries, and frozen evidence identity. Whole review history is not embedded or reacquired. +- Both the current and previous task-review records retain `required: true`, `reviewer_independent: true`, and `review_target_kind: task`; the adapter does not infer or overwrite those ownership facts. The accepted repaired target names the completed task and its `source_tree` plus `reviewed_head` must equal the helper-observed Git tree and head. - Material redesign or changed authority, scope, acceptance, decomposition, or validation allocation uses a fresh `initial` review with `review_reset`; it may not reuse the repair reviewer identity. ## Planning verification diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 9041c38..76b7a9b 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1758,6 +1758,23 @@ def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, raise SystemExit(f"Task acceptance review is invalid: {error}") from error if validated.verdict != "accepted": raise SystemExit("Review-required task cannot complete without an accepted task review") + if validated.target_identity["artifact_id"] != str(task.get("task_id")): + raise SystemExit("Task acceptance review target identity does not match the completed task") + if validated.review_mode == "repair": + repositories = [ + item for item in _as_list(handoff.get("repository")) + if isinstance(item, dict) and item.get("target_kind") == "git-backed" + ] + if len(repositories) != 1: + raise SystemExit("Task repair review requires one observed Git repository identity") + root = Path(str(repositories[0].get("root") or "")).expanduser().resolve() + head = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], capture_output=True, text=True) + tree = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD^{tree}"], capture_output=True, text=True) + if head.returncode or tree.returncode: + raise SystemExit("Task repair review Git identity is unavailable") + if (review.get("reviewed_head") != head.stdout.strip() + or validated.target_identity["source_tree"] != tree.stdout.strip()): + raise SystemExit("Task repair review does not match the observed Git head/tree identity") def _yaml_scalar(value: Any) -> str: diff --git a/scripts/orchestration/review_runtime.py b/scripts/orchestration/review_runtime.py index dc3bfc0..9113720 100644 --- a/scripts/orchestration/review_runtime.py +++ b/scripts/orchestration/review_runtime.py @@ -957,26 +957,37 @@ def _task_review_as_stage(value: Mapping[str, Any]) -> dict[str, Any]: def validate_task_acceptance_review(value: Mapping[str, Any]) -> StageReviewV1: """Validate a task acceptance review and its one bounded predecessor.""" record = _mapping(value, "task acceptance_review") - if (record.get("required") is not True or record.get("review_target_kind") != "task" - or record.get("reviewer_independent") is not True): - raise ReviewContractError("task acceptance_review requires review_target_kind: task") - mode = _enum(record.get("review_mode"), REVIEW_MODES, "task acceptance_review.review_mode") - current = _task_review_as_stage(record) + current = validate_task_review_record(record) + mode = current.review_mode previous = record.get("previous_review") if mode == "repair": if not isinstance(previous, Mapping): raise ReviewContractError("task repair review requires its exact previous_review") if "previous_review" in previous: raise ReviewContractError("task repair review may carry exactly one previous_review; older history stays lazy") - return validate_review_sequence(current, previous_review=_task_review_as_stage(previous)) + validate_task_review_record(previous) + return validate_review_sequence(_task_review_as_stage(record), previous_review=_task_review_as_stage(previous)) if record.get("review_reset") is not None: if not isinstance(previous, Mapping): raise ReviewContractError("task initial reset requires its exact previous_review") + validate_task_review_record(previous) reset = _mapping(record["review_reset"], "review_reset") return validate_review_sequence( - current, previous_review=_task_review_as_stage(previous), material_change=str(reset.get("reason_class")) + _task_review_as_stage(record), previous_review=_task_review_as_stage(previous), material_change=str(reset.get("reason_class")) + ) + return validate_review_sequence(_task_review_as_stage(record)) + + +def validate_task_review_record(value: Mapping[str, Any]) -> StageReviewV1: + """Validate one native task-review record without traversing history.""" + record = _mapping(value, "task acceptance_review") + if (record.get("required") is not True or record.get("review_target_kind") != "task" + or record.get("reviewer_independent") is not True): + raise ReviewContractError( + "task acceptance_review requires required: true, reviewer_independent: true, and review_target_kind: task" ) - return validate_review_sequence(current) + _enum(record.get("review_mode"), REVIEW_MODES, "task acceptance_review.review_mode") + return validate_stage_review(_task_review_as_stage(record)) def validate_stage_reviews( diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 3a97197..6fa8b77 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -2409,10 +2409,23 @@ def test_rf_task_repair_completion_rejects_unsequenced_acceptance_review() -> No execution_context.validate_executor_result_for_task(handoff, brief) -def test_rf_task_repair_completion_rejects_stale_repair_frontier() -> None: +def test_rf_task_repair_completion_rejects_stale_repair_frontier(tmp_path: Path) -> None: brief, handoff = _repair_completion_fixture() - old = {"artifact_id": "task-rf", "revision": "1", "sha256": "1" * 64, "source_tree": None} - new = {"artifact_id": "task-rf", "revision": "1", "sha256": "2" * 64, "source_tree": None} + git(tmp_path, "init", "-q") + git(tmp_path, "config", "user.name", "Test") + git(tmp_path, "config", "user.email", "test@example.com") + source = tmp_path / "source.py" + source.write_text("OLD = True\n") + git(tmp_path, "add", ".") + git(tmp_path, "commit", "-qm", "old") + old_tree = git(tmp_path, "rev-parse", "HEAD^{tree}") + source.write_text("NEW = True\n") + git(tmp_path, "add", ".") + git(tmp_path, "commit", "-qm", "repaired") + repaired_head = git(tmp_path, "rev-parse", "HEAD") + repaired_tree = git(tmp_path, "rev-parse", "HEAD^{tree}") + old = {"artifact_id": "task-rf", "revision": "1", "sha256": "1" * 64, "source_tree": old_tree} + new = {"artifact_id": "task-rf", "revision": "1", "sha256": "2" * 64, "source_tree": repaired_tree} prior = { "required": True, "reviewer_independent": True, "verdict": "repair", "review_id": "review-prior", "review_mode": "initial", "review_target_kind": "task", "repair_frontier": None, "review_reset": None, @@ -2431,6 +2444,7 @@ def test_rf_task_repair_completion_rejects_stale_repair_frontier() -> None: from review_runtime import review_evidence_identity handoff["acceptance_review"] = { "required": True, "reviewer_independent": True, "verdict": "accept", "review_id": "review-repair", + "reviewed_head": repaired_head, "review_mode": "repair", "review_target_kind": "task", "review_reset": None, "repair_frontier": { "prior_review_id": "review-prior", "blocking_finding_ids": ["RF-FINDING-1"], @@ -2445,11 +2459,23 @@ def test_rf_task_repair_completion_rejects_stale_repair_frontier() -> None: "started_at": "2026-09-06T00:02:00Z", "completed_at": "2026-09-06T00:03:00Z", "staleness": {"is_stale": False, "reason": None, "supersedes": None}, } + handoff["repository"] = [{"root": str(tmp_path), "target_kind": "git-backed", "preflight_kind": "git-clean-worktree", "baseline": "initial", "status": "clean"}] assert execution_context.validate_executor_result_for_task(handoff, brief)["result_state"] == "completed" stale = deepcopy(handoff) stale["acceptance_review"]["target_identity"] = {**new, "sha256": "3" * 64} with pytest.raises(SystemExit, match="repaired identity|frontier"): execution_context.validate_executor_result_for_task(stale, brief) + for field, value in (("required", False), ("reviewer_independent", False), ("review_target_kind", "wrong-kind")): + invalid_prior = deepcopy(handoff) + invalid_prior["acceptance_review"]["previous_review"][field] = value + with pytest.raises(SystemExit, match="previous|task acceptance|review_target_kind"): + execution_context.validate_executor_result_for_task(invalid_prior, brief) + fabricated = deepcopy(handoff) + fabricated_identity = {**new, "sha256": "3" * 64, "source_tree": "f" * 40} + fabricated["acceptance_review"]["target_identity"] = fabricated_identity + fabricated["acceptance_review"]["repair_frontier"]["repaired_identity"] = fabricated_identity + with pytest.raises(SystemExit, match="head|tree|Git identity"): + execution_context.validate_executor_result_for_task(fabricated, brief) def test_no_review_update_stays_closure_eligible_when_handoff_self_upgrades() -> None: From cfa75a3ec64286d29b63e4e549790f22ebfd2ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 02:04:50 +0800 Subject: [PATCH 04/36] feat(orchestration): require subagent task ownership --- .../contract/handoff-executor-result-v1.md | 15 +- references/assets/orchestration/workflow.md | 10 +- references/assets/template/AGENTS.md | 3 - references/assets/template/bootstrap.yaml | 1 - references/assets/template/project.yaml | 1 - references/evals/orchestration/evals.json | 24 +-- rules/orchestration/orch-handoff-required.md | 8 +- .../wb-project-context-preflight.md | 2 +- scripts/orchestration/task_ownership.py | 145 +++++++++++++++++ scripts/work-bundle/control_plane.py | 2 - scripts/work-bundle/core.py | 36 ----- scripts/work-bundle/dispatcher.py | 6 +- scripts/work-bundle/migration.py | 1 - scripts/work-bundle/project.py | 35 ---- skills/orch-create-handoff/SKILL.md | 6 +- skills/orch-doctor/SKILL.md | 26 +-- skills/orch-execute-plan/SKILL.md | 13 +- skills/wb-initialize-project/SKILL.md | 21 +-- tests/test_control_plane_v4.py | 24 +-- tests/test_multi_repository_member.py | 6 +- .../test_orchestration_skill_rule_boundary.py | 17 +- .../test_orchestration_workflow_contracts.py | 10 +- tests/test_project_initialization.py | 77 ++------- tests/test_registry_layout_migration.py | 18 ++- tests/test_wor108_subagent_ownership.py | 149 ++++++++++++++++++ 25 files changed, 422 insertions(+), 234 deletions(-) create mode 100644 scripts/orchestration/task_ownership.py create mode 100644 tests/test_wor108_subagent_ownership.py diff --git a/references/assets/orchestration/contract/handoff-executor-result-v1.md b/references/assets/orchestration/contract/handoff-executor-result-v1.md index 772e959..1d00189 100644 --- a/references/assets/orchestration/contract/handoff-executor-result-v1.md +++ b/references/assets/orchestration/contract/handoff-executor-result-v1.md @@ -142,12 +142,11 @@ codegraph: reason: null | no-index | sync-failed | not-source-code | blocked delegation_evidence: - delegated: true | false - surface: visible-thread | visible-worktree | visible-thread-and-worktree | single-agent-fallback | blocked - visible_reference: null - internal_spawn_used_for_task_delegation: false - internal_workers_used_for_support: false - fallback_reason: null + delegated: true + owner_kind: subagent + agent_id: agent-or-provider-identity + run_id: task-run-identity + mechanism: host-native | execution-flow allocation_evidence: allocated_rules: @@ -178,7 +177,7 @@ allocation_evidence: - `repository` is required when repository preflight, accepted baseline, changed paths, or blocker state matters for continuation. - `repository[].metadata` is required when project metadata baseline was used for target resolution, branch checks, commit checks, or CodeGraph policy decisions. - `codegraph` is required when source-code inspection or edits were in scope. Keep it compact: `root`, `applicable`, `up_to_date`, and required fallback or blocker facts are enough unless a failure needs detail. -- `delegation_evidence` is required when task, phase, or plan ownership was delegated or when the execution path needs proof that invisible internal spawn did not own delegation. +- `delegation_evidence` is required for every task executor-result and is optional for non-task scopes. Its five-field closed shape proves mandatory subagent ownership without UI-specific semantics. - `allocation_evidence` is required when allocated_rules or allocated_skills materially shaped execution or when an allocated rule/skill was unavailable, skipped, stale, or inapplicable. ## Forbidden Executor-Result Fields @@ -206,7 +205,7 @@ Compact handoffs must not weaken safety gates: - Repository evidence must preserve root, target kind, preflight kind, baseline, and clean or blocked result when applicable. - Metadata evidence must preserve repository id, expected and actual branch, expected and actual commit, branch status, commit status, and baseline status when project metadata preflight applies. - CodeGraph evidence must preserve no-index fallback, sync-failed, stale, or blocker facts when applicable. -- Delegation evidence must preserve visible surface, visible reference when available, and `internal_spawn_used_for_task_delegation: false`. +- Delegation evidence must preserve delegated state, `owner_kind: subagent`, minimum agent/run identity, and `host-native|execution-flow` mechanism. UI, visibility, fallback, controller-owner, and internal-worker fields are invalid. - Validation evidence must list exact commands or inspections and their result. Executor-authored `result`, `exit_code`, or an equivalently named receipt block is corroboration, not independent proof and not authority for `Completed`. Direct helper observation in the bound worktree is the terminal evidence. - Task-fit evidence must prove the executor followed the compiled brief and assigned task. Full specification, root-plan, and phase inspection is an escalation path when compiled context is inconsistent. - Acceptance-review evidence must identify review independence, the reviewed tree, verdict, and blocking or advisory findings. diff --git a/references/assets/orchestration/workflow.md b/references/assets/orchestration/workflow.md index 5cee2d0..442e2ad 100644 --- a/references/assets/orchestration/workflow.md +++ b/references/assets/orchestration/workflow.md @@ -178,7 +178,9 @@ their ordering priority. This does not introduce an initial-versus-repair fronti scheduler selects executable task -> compile task brief -> choose provider-neutral capability - -> implement with declared methodology + -> bind mandatory subagent owner or fail closed before mutation + -> dispatch every planner-approved disjoint ready task before waiting + -> subagent implements with declared methodology -> run fresh task-local validation -> write executor-result handoff -> validate-executor-result @@ -189,11 +191,11 @@ scheduler selects executable task -> Completed ``` -Executors own implementation, task-local verification, and executor-result evidence, including a task-local knowledge disposition of `none`, `update`, `supersede`, or `reclassify`. They never invoke persistence or read knowledge. Reviewers own acceptance judgment for the accepted Truth Basis, requirement fit, correctness, edge cases, test oracle, disposition, unnecessary complexity, allocated obligations, and validation sufficiency. Schedulers own dependencies, barriers, context compilation, delegation, and evidence shape; they do not perform code-quality review. +Subagent executors own every implementation and repair mutation, task-local verification, and executor-result evidence, including a task-local knowledge disposition of `none`, `update`, `supersede`, or `reclassify`. They never invoke persistence or read knowledge. Reviewers own acceptance judgment for the accepted Truth Basis, requirement fit, correctness, edge cases, test oracle, disposition, unnecessary complexity, allocated obligations, and validation sufficiency. Schedulers own dependencies, barriers, context compilation, neutral subagent binding, validation routing, and evidence shape; they do not perform code-quality review or mutate task write scope. -Visible multi-agent subagents are preferred only when user/environment policy allows and write scopes are disjoint. Invisible helper workers may support bounded analysis but never own delegated task implementation. When independent subagents are unavailable, record `reviewer_independent: false` and perform an explicit reduced-independence second pass. +Selecting `orch-execute-plan` requires a subagent owner for every task without a separate user opt-in. Host-native and Execution-Flow-routed subagents are equivalent; evidence records only the minimum agent/run identity and mechanism. If none is available, execution fails closed before task mutation. Independent disjoint tasks in distinct execution workspaces dispatch before any wait; dependent, overlapping, or same-workspace tasks serialize. -On `repair`, return blocking findings with the same brief and current diff, make the smallest repair, rerun claim-relevant validation, regenerate the package from the original base, and review again. After two failed low-cost repair rounds, escalate the capability tier; if evidence indicates a plan or specification defect, stop the retry loop and route the typed blocker. +On `repair`, return blocking findings with the same brief and current diff to the task-owning subagent. It makes the smallest repair, reruns claim-relevant validation, regenerates the package from the original base, and reviews again. If no subagent is available, fail closed before repair mutation. After two failed low-cost repair rounds, escalate the capability tier; if evidence indicates a plan or specification defect, stop the retry loop and route the typed blocker. A task becomes `Completed` only when implementation criteria, fresh validation, a valid executor-result handoff, and a passing `validate-executor-result` check all exist. `Completed` does not require `verdict: accept` unless review was required. Phase and plan status derive from accepted children plus declared dependency and barrier gates. diff --git a/references/assets/template/AGENTS.md b/references/assets/template/AGENTS.md index 395e2d0..c893f1d 100644 --- a/references/assets/template/AGENTS.md +++ b/references/assets/template/AGENTS.md @@ -23,10 +23,8 @@ must: - resolve `work_bundle_root` from `$work_bundle_config_root/bootstrap.yaml` -> `work_bundle_root` - resolve project registry from `$work_bundle_config_root/bootstrap.yaml` -> `project_registry` - resolve skill registry from `$work_bundle_config_root/bootstrap.yaml` -> `skill_registry` -- resolve effective `prefer_subagent` as `.work-bundle/project.yaml` -> `prefer_subagent`, then `$work_bundle_config_root/bootstrap.yaml` -> `prefer_subagent`, then `false` - before material implementation, establish or consume one Truth Basis containing purpose, as-is evidence, accepted decision authority, expected delta, and conflict status; after preflight and source grounding, lightweight planning runs one bounded `ks-what-is-helpful` gateway and records accepted authority or evidence-backed `none relevant`, while heavy execution compiles carried authority without executor retrieval - after each meaningful validated move, record a knowledge disposition of `none`, `update`, `supersede`, or `reclassify`; the lightweight completion owner resolves its approved `ks-*` follow-up, while heavy executors return task-local evidence only and final orchestration review owns heavy-path persistence follow-up -- treat `prefer_subagent` as permission to prefer sub-agent scheduling only when normal execution safety, write-scope, dependency, and fallback checks pass - use `work_bundle_root` only for toolkit assets, builtin skills, builtin rules, and references - use `work_bundle_config_root` only for non-project runtime state produced by tool use - resolve workspace-owned metadata, rules, knowledge, orchestration, `AGENTS.md`, `script/index.yaml`, and `credentials/credentials.yaml` from `workspace_root` in both workspace modes @@ -47,7 +45,6 @@ must_not: - treat utility discovery as permission to execute a script - inspect or transfer credential values through chat, prompts, subagent messages, tool arguments/results, terminal output, logs, handoffs, knowledge, or orchestration artifacts - infer registry paths without reading `bootstrap.yaml` when registry access is required -- let `prefer_subagent` bypass repository preflight, sub-agent capability checks, disjoint write-scope checks, dependency checks, or single-agent fallback - treat rule-store scope (`toolkit`, `global`, `project`) as separate from rule area directories such as `work-bundle`, `keep-summarizing`, and `orchestration` ## Rule Loading diff --git a/references/assets/template/bootstrap.yaml b/references/assets/template/bootstrap.yaml index 608d70d..0899d2b 100644 --- a/references/assets/template/bootstrap.yaml +++ b/references/assets/template/bootstrap.yaml @@ -3,4 +3,3 @@ authority: canonical work_bundle_root: __WORK_BUNDLE_ROOT__ project_registry: "$work_bundle_config_root/registry/projects.yaml" skill_registry: "$work_bundle_config_root/registry/skill-registry.yaml" -prefer_subagent: false diff --git a/references/assets/template/project.yaml b/references/assets/template/project.yaml index 21a6cf3..9127b2c 100644 --- a/references/assets/template/project.yaml +++ b/references/assets/template/project.yaml @@ -4,7 +4,6 @@ workspace_root: workspace_mode: project_root: industry: -prefer_subagent: false metadata_compatibility: readable_versions: [2, 3] diff --git a/references/evals/orchestration/evals.json b/references/evals/orchestration/evals.json index 883662e..9fdc0dd 100644 --- a/references/evals/orchestration/evals.json +++ b/references/evals/orchestration/evals.json @@ -22,7 +22,7 @@ { "id": 4, "prompt": "Execute the next task in an existing plan.", - "expected_output": "Selects execute-plan, checks sub-agent support, uses the single-agent fallback when sub-agents are unavailable, executes one task, updates task and phase task status, reports concise execution result, and requires an executor handoff.", + "expected_output": "Selects execute-plan, requires a subagent owner before task mutation, executes through that subagent when available, and otherwise fails closed with workspace-blocked without changing task scope.", "files": [] }, { @@ -34,7 +34,7 @@ { "id": 6, "prompt": "Execute a phase whose next two tasks are independent and have disjoint write scopes, in an environment with sub-agents.", - "expected_output": "Selects execute-plan, uses the sub-agent scheduler path, delegates the independent tasks to multiple sub-agents, waits for completion, validates executor handoffs against task/phase/plan/spec, updates task statuses, and continues scheduling.", + "expected_output": "Selects execute-plan, binds the independent tasks to separate subagents and isolated execution workspaces, dispatches both before awaiting either, validates executor handoffs against task/phase/plan/spec, updates task statuses, and continues scheduling.", "files": [] }, { @@ -64,7 +64,7 @@ { "id": 11, "prompt": "Doctor the orchestrator skill for installation health, malformed orch skills, and workflow bias.", - "expected_output": "Selects orch-doctor, uses dev-rules-doctor first, runs the develop-rules doctor command, audits orch skill files, workflow, evals, helper commands, and cross-skill v3 gateway wiring read-only, checks that orch-execute-plan preserves both sub-agent scheduler and single-agent fallback paths, verifies archival belongs to orch-review-plan, and reports concrete repairs without changing files.", + "expected_output": "Selects orch-doctor, uses dev-rules-doctor first, runs the develop-rules doctor command, audits orch skill files, workflow, evals, helper commands, and cross-skill v3 gateway wiring read-only, checks mandatory subagent ownership and fail-closed behavior, verifies archival belongs to orch-review-plan, and reports concrete repairs without changing files.", "files": [] }, { @@ -130,13 +130,13 @@ { "id": 22, "prompt": "Execute a plan task with a delegated sub-agent, then prepare its executor-result handoff after implementation reveals a task-scoped gap.", - "expected_output": "Preserves preflight, accepted baseline, dependencies, and write scopes; compiles a self-contained task brief; repairs the task-local gap, reruns fresh validation, writes a sparse handoff, compiles a bounded review package, and requires an accept review before completion.", + "expected_output": "Preserves preflight, accepted baseline, dependencies, write scopes, and the same subagent task owner; the subagent repairs the task-local gap, reruns fresh validation, writes a sparse handoff with neutral ownership evidence, compiles a bounded review package, and requires an accept review before completion.", "files": [] }, { "id": 23, - "prompt": "Execute a source-inspection task when the target repository has no .codegraph directory and effective prefer_subagent resolves to false.", - "expected_output": "Selects execute-plan, records that CodeGraph is skipped because the repository is not indexed, preserves text-search fallback, uses the single-agent fallback for exactly one task, keeps execution no-retrieval, does not require any additional runtime preference file, and does not bypass repository preflight, dependency checks, write-scope checks, or executor-result handoff requirements.", + "prompt": "Execute a source-inspection task when the target repository has no .codegraph directory and legacy metadata contains prefer_subagent: false.", + "expected_output": "Selects execute-plan, records that CodeGraph is skipped because the repository is not indexed, preserves text-search fallback, ignores the legacy preference field, requires subagent ownership, keeps execution no-retrieval, and does not bypass repository preflight, dependency checks, write-scope checks, or executor-result handoff requirements.", "files": [] }, { @@ -171,20 +171,20 @@ }, { "id": 29, - "prompt": "Execute a scheduler wave with visible multi-agent subagents available and safe disjoint task scopes.", - "expected_output": "Delegates task ownership only to visible multi-agent subagents, records compact delegation_evidence with each visible_reference when provided, sets internal_spawn_used_for_task_delegation to false, validates sparse executor-result handoffs, rejects cross-conversation delegation as a task ownership vehicle, and preserves preflight and scope gates.", + "prompt": "Execute a scheduler wave with host-native and Execution-Flow-routed subagents available and safe disjoint task scopes.", + "expected_output": "Treats both subagent mechanisms equivalently, records only delegated state, subagent owner kind, agent/run identity, and mechanism in compact delegation_evidence, dispatches disjoint tasks before waiting, and preserves preflight and scope gates.", "files": [] }, { "id": 30, - "prompt": "Execute a plan task when only invisible internal spawn workers are available for delegation.", - "expected_output": "Rejects invisible internal spawn workers as the task delegation vehicle, uses single-agent fallback for one task when valid or reports a delegation-visibility blocker, and does not mark delegated execution complete without compact visible delegation_evidence.", + "prompt": "Execute a plan task when no supported subagent execution mechanism is available.", + "expected_output": "Fails closed with workspace-blocked before any task mutation and does not substitute controller or single-agent implementation.", "files": [] }, { "id": 31, - "prompt": "Execute a delegated task while an internal helper worker is used only for bounded snippet comparison.", - "expected_output": "Allows the helper-worker support because visible multi-agent subagent ownership remains intact, records internal helper usage in compact delegation_evidence, keeps internal_spawn_used_for_task_delegation false, and requires the visible executor handoff to own validation and task_fit_check evidence.", + "prompt": "The orchestration controller directly patches a task-owned file and all task validations pass.", + "expected_output": "Rejects task acceptance with review-blocked because controller mutation violates mandatory subagent ownership; green validation cannot override ownership provenance.", "files": [] }, { diff --git a/rules/orchestration/orch-handoff-required.md b/rules/orchestration/orch-handoff-required.md index 4e723b6..2330a08 100644 --- a/rules/orchestration/orch-handoff-required.md +++ b/rules/orchestration/orch-handoff-required.md @@ -28,7 +28,7 @@ Require compact executor-result handoffs before reporting execution complete or - For executor-result handoffs, preserve execution safety evidence where applicable: repository preflight or accepted-baseline evidence, validation evidence, drift/gap verification, unresolved blockers, and changed-path evidence. - For executor-result handoffs, include compact CodeGraph evidence when source-code inspection or edits were in scope. The evidence must be no larger than `root`, `applicable`, `up_to_date`, and required fallback or blocker facts unless a failure needs more detail. - For executor-result handoffs, explicitly record no-index fallback when a target repository lacks `.codegraph/`; do not omit CodeGraph evidence silently when source-code work was in scope. -- Use `delegation_evidence` only as proof of task ownership delegation when applicable. It records delegated flag, visible surface, `visible_reference` when available, `internal_spawn_used_for_task_delegation: false`, internal helper-worker usage if any, and fallback or blocker reason when visible delegation was unavailable or unsafe. +- Use `delegation_evidence` as neutral proof of mandatory task ownership. It records only delegated state, `owner_kind: subagent`, minimum agent/run identity, and provider-neutral `host-native|execution-flow` mechanism. - For contract-decoupled task handoffs, include compact `contract_decoupling` evidence: common contract group, common contracts checked, validation scope, `peer_implementation_validation_used: false`, and forbidden peer validation result. - For barrier participants, include compact `barrier` evidence with barrier id, participant role, readiness `reached|blocked`, and whether convergence remains pending. - For convergence owners, include compact `barrier` and `convergence` evidence showing every participant completed or blocked with executor-result handoffs before joint validation began. @@ -49,10 +49,10 @@ Require compact executor-result handoffs before reporting execution complete or - Omit changed files, validation evidence, unresolved blockers, or `task_fit_check` when they are applicable to the completed or partial result. - Claim a clean result without recording the compiled brief and assigned task checked, repairs made, and recheck outcome. - Omit applicable compact CodeGraph fallback, up-to-date, or blocker evidence from executor-result handoffs for source-code work. -- Omit visible `delegation_evidence` from executor-result handoffs when plan, phase, or task ownership was delegated, or record contradictory delegation evidence such as `internal_spawn_used_for_task_delegation: true`. +- Omit neutral `delegation_evidence` from a task executor-result handoff, record a non-subagent owner, or add UI, visibility, fallback, or internal-worker fields to ownership provenance. - Omit contract-only validation evidence from a contract-decoupled task handoff, or report peer implementation validation as used before barrier release. - Omit barrier readiness evidence from a barrier participant handoff, or schedule convergence without participant completed/blocked handoffs. -- Skip handoff creation because sub-agents, fallback mode, or partial completion made the outcome informal. +- Skip handoff creation because subagent execution blocked or partial completion made the outcome informal. - Create new active `handoff-orch-*` artifacts as continuation output. ## Validation @@ -63,7 +63,7 @@ Require compact executor-result handoffs before reporting execution complete or - Confirm executor-result handoffs created during execution did not invoke knowledge retrieval. - Confirm executor-result handoffs identify the compiled brief and assigned task checked; include findings, repairs, and final recheck evidence; and escalate to full source artifacts when compiled context is inconsistent. - Confirm executor-result handoffs include applicable compact CodeGraph evidence for every source-code target: root, applicability, `up_to_date`, and no-index, sync-failed, stale, or blocker facts when used. -- Confirm executor-result handoffs include `delegation_evidence` when delegation was used, including `visible_reference` when available and `internal_spawn_used_for_task_delegation: false`. +- Confirm task executor-result handoffs include closed neutral `delegation_evidence` with delegated state, subagent owner kind, agent/run identity, and provider-neutral mechanism. - Confirm contract-decoupled task handoffs include common-contract validation scope and `peer_implementation_validation_used: false`. - Confirm barrier participant and convergence-owner handoffs include readiness or release evidence by applicability. - Confirm the handoff index entry reflects the new or updated handoff. diff --git a/rules/work-bundle/wb-project-context-preflight.md b/rules/work-bundle/wb-project-context-preflight.md index 5c9db3a..8c7e214 100644 --- a/rules/work-bundle/wb-project-context-preflight.md +++ b/rules/work-bundle/wb-project-context-preflight.md @@ -50,7 +50,7 @@ Require agents to resolve the containing `workspace_root`, portable topology, an - Do not apply the metadata-v3 project-local authority model to metadata v4. - Do not write project registry state under `work_bundle_root` or `project_root`. - Do not load durable knowledge files solely to satisfy project-structure awareness. -- Do not let `prefer_subagent` bypass project context preflight, branch baseline checks, dependency checks, write-scope checks, validation, handoff, or single-agent fallback rules. +- Do not let legacy scheduling-preference metadata affect mandatory subagent ownership, project context preflight, branch baseline checks, dependency checks, write-scope checks, validation, or handoff rules. - Do not run destructive Git operations such as cleanup, reset, stash, or force push to satisfy preflight. - Do not initialize CodeGraph for a repository root that lacks `.codegraph/`. - Do not infer lifecycle Git stage or commit authority from initialization, doctor, repair, migration, or validation authority. diff --git a/scripts/orchestration/task_ownership.py b/scripts/orchestration/task_ownership.py new file mode 100644 index 0000000..07dd99e --- /dev/null +++ b/scripts/orchestration/task_ownership.py @@ -0,0 +1,145 @@ +"""Provider-neutral subagent ownership and ready-wave scheduling.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable, Iterable, Mapping, Sequence, TypeVar + + +PROVENANCE_FIELDS = frozenset({"delegated", "owner_kind", "agent_id", "run_id", "mechanism"}) +SUBAGENT_MECHANISMS = frozenset({"host-native", "execution-flow"}) + + +class OwnershipBlocker(RuntimeError): + """Typed orchestration blocker raised before mutation or acceptance.""" + + def __init__(self, code: str, reason: str) -> None: + self.code = code + self.reason = reason + super().__init__(f"{code}: {reason}") + + +@dataclass(frozen=True) +class TaskCandidate: + task_id: str + dependencies: tuple[str, ...] + write_scope: tuple[str, ...] + execution_workspace: str + + def __post_init__(self) -> None: + if not self.task_id.strip(): + raise ValueError("task_id must be non-empty") + if not self.write_scope or any(not str(path).strip() for path in self.write_scope): + raise ValueError("write_scope must contain explicit paths") + if not self.execution_workspace.strip(): + raise ValueError("execution_workspace must be non-empty") + + +@dataclass(frozen=True) +class DispatchWaveResult: + dispatched: tuple[str, ...] + results: tuple[object, ...] + + +def _identifier(value: object, field: str) -> str: + normalized = str(value or "").strip() + if not normalized: + raise OwnershipBlocker("workspace-blocked", f"subagent {field} is missing") + return normalized + + +def normalize_subagent_provenance( + evidence: Mapping[str, object] | None, + *, + operation: str = "implementation", +) -> dict[str, object]: + """Validate the closed, provider-neutral task ownership receipt.""" + + if operation not in {"implementation", "repair"}: + raise ValueError("operation must be implementation or repair") + if not isinstance(evidence, Mapping): + raise OwnershipBlocker("workspace-blocked", f"{operation} requires subagent ownership") + if set(evidence) != PROVENANCE_FIELDS: + raise OwnershipBlocker("workspace-blocked", "subagent ownership provenance is not closed") + if evidence.get("delegated") is not True or evidence.get("owner_kind") != "subagent": + raise OwnershipBlocker("workspace-blocked", f"{operation} requires subagent ownership") + mechanism = _identifier(evidence.get("mechanism"), "mechanism") + if mechanism not in SUBAGENT_MECHANISMS: + raise OwnershipBlocker("workspace-blocked", "subagent mechanism is unsupported") + return { + "delegated": True, + "owner_kind": "subagent", + "agent_id": _identifier(evidence.get("agent_id"), "agent_id"), + "run_id": _identifier(evidence.get("run_id"), "run_id"), + "mechanism": mechanism, + } + + +def _scope_matches(path: str, scope: str) -> bool: + left = path.strip().removeprefix("./").rstrip("/") + right = scope.strip().removeprefix("./").rstrip("/") + return left == right or left.startswith(right + "/") or right.startswith(left + "/") + + +def _scopes_overlap(left: Sequence[str], right: Sequence[str]) -> bool: + return any(_scope_matches(a, b) for a in left for b in right) + + +def validate_task_acceptance_ownership( + *, + delegation_evidence: Mapping[str, object] | None, + mutation_events: Iterable[Mapping[str, object]], + write_scope: Sequence[str], + validations_passed: bool, + operation: str = "implementation", +) -> dict[str, object]: + """Reject controller-authored task-scope changes even after green validation.""" + + provenance = normalize_subagent_provenance(delegation_evidence, operation=operation) + for event in mutation_events: + actor_kind = str(event.get("actor_kind") or "").strip() + paths = event.get("paths") + if actor_kind != "controller" or not isinstance(paths, Sequence) or isinstance(paths, (str, bytes)): + continue + changed = [str(path) for path in paths] + if _scopes_overlap(changed, write_scope): + raise OwnershipBlocker( + "review-blocked", + "controller mutated task-owned implementation scope", + ) + if not validations_passed: + raise OwnershipBlocker("validation-blocked", "task validation has not passed") + return provenance + + +def _ready_wave(tasks: Sequence[TaskCandidate], completed: set[str]) -> list[TaskCandidate]: + ready = [task for task in tasks if set(task.dependencies).issubset(completed)] + wave: list[TaskCandidate] = [] + for task in ready: + if any(task.execution_workspace == selected.execution_workspace for selected in wave): + continue + if any(_scopes_overlap(task.write_scope, selected.write_scope) for selected in wave): + continue + wave.append(task) + return wave + + +Handle = TypeVar("Handle") + + +def dispatch_ready_wave( + tasks: Sequence[TaskCandidate], + *, + completed: set[str], + subagents_available: bool, + dispatch: Callable[[TaskCandidate], Handle], + wait: Callable[[Handle], object], +) -> DispatchWaveResult: + """Dispatch every safe ready task before awaiting any returned handle.""" + + if not subagents_available: + raise OwnershipBlocker("workspace-blocked", "subagent execution is unavailable") + wave = _ready_wave(tasks, completed) + handles = [dispatch(task) for task in wave] + results = tuple(wait(handle) for handle in handles) + return DispatchWaveResult(tuple(task.task_id for task in wave), results) diff --git a/scripts/work-bundle/control_plane.py b/scripts/work-bundle/control_plane.py index 5ab1bfe..2b52d0c 100644 --- a/scripts/work-bundle/control_plane.py +++ b/scripts/work-bundle/control_plane.py @@ -658,7 +658,6 @@ def _render_v4( " operation_policy: inherit", ] ) - lines.append(f"prefer_subagent: {str(_parse_bool(_yaml_scalar(v3_text, 'prefer_subagent'))).lower()}") lines.append(_agents_contract_block()) lines.extend(_portable_unknown_blocks(v3_text)) return "\n".join(lines).rstrip() + "\n", workspace_id, repositories @@ -1079,7 +1078,6 @@ def _render_new_v4_for_mode( f" required: {str(bool(repository.get('required', True))).lower()}", " operation_policy: inherit", ]) - lines.append("prefer_subagent: false") lines.append(_agents_contract_block()) return "\n".join(lines) + "\n", workspace_id diff --git a/scripts/work-bundle/core.py b/scripts/work-bundle/core.py index 064dfa0..a3b4c2d 100644 --- a/scripts/work-bundle/core.py +++ b/scripts/work-bundle/core.py @@ -56,7 +56,6 @@ execution-workspace-cleanup-owned --runtime-root --workspace-id ... execution-workspace-doctor-stale [--runtime-root ] [--cleanup] instruction-audit --root [--soft-threshold-words ] - set-prefer-subagent --scope [--project-root ] generate-project-metadata-profile --input --output merge-project-metadata-profile --current --incoming --output validate-project-metadata-profile @@ -138,17 +137,6 @@ def compact_yaml_map(text: str) -> dict[str, str]: return data -def yaml_bool(value: object, default: bool = False) -> bool: - if value is None: - return default - normalized = str(value).strip().lower() - if normalized in {'true', 'yes', 'on', '1'}: - return True - if normalized in {'false', 'no', 'off', '0', ''}: - return False - return default - - def utc_now_rfc3339() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z') @@ -176,37 +164,13 @@ def resolve_bootstrap_runtime() -> dict[str, object]: config_root = work_bundle_config_root() bootstrap_path = config_root / GLOBAL_BOOTSTRAP_FILE_NAME resolved = resolve_work_bundle_root() - bootstrap = compact_yaml_map(read(bootstrap_path)) if bootstrap_path.is_file() else {} - global_prefer_subagent = yaml_bool(bootstrap.get('prefer_subagent'), False) return { 'work_bundle_config_root': str(config_root), 'global_bootstrap_path': str(bootstrap_path), 'global_bootstrap_exists': bootstrap_path.is_file(), 'resolved_work_bundle_root': str(resolved) if resolved else None, - 'prefer_subagent': global_prefer_subagent, } def project_metadata_path(project_root: Path) -> Path: return project_root.expanduser().resolve() / '.work-bundle' / 'project.yaml' - - -def resolve_effective_prefer_subagent(project_root: Path | None = None) -> dict[str, object]: - config_root = work_bundle_config_root() - bootstrap_path = config_root / GLOBAL_BOOTSTRAP_FILE_NAME - bootstrap = compact_yaml_map(read(bootstrap_path)) if bootstrap_path.is_file() else {} - global_prefer_subagent = yaml_bool(bootstrap.get('prefer_subagent'), False) - - project_path = project_metadata_path(project_root) if project_root is not None else None - project_metadata = compact_yaml_map(read(project_path)) if project_path and project_path.is_file() else {} - has_project_override = 'prefer_subagent' in project_metadata - effective = yaml_bool(project_metadata.get('prefer_subagent'), global_prefer_subagent) if has_project_override else global_prefer_subagent - source = 'project' if has_project_override else ('global' if 'prefer_subagent' in bootstrap else 'default') - return { - 'prefer_subagent': effective, - 'source': source, - 'project_prefer_subagent': yaml_bool(project_metadata.get('prefer_subagent'), False) if has_project_override else None, - 'global_prefer_subagent': global_prefer_subagent, - 'global_bootstrap_path': str(bootstrap_path), - 'project_metadata_path': str(project_path) if project_path else None, - } diff --git a/scripts/work-bundle/dispatcher.py b/scripts/work-bundle/dispatcher.py index 8486660..851c83d 100644 --- a/scripts/work-bundle/dispatcher.py +++ b/scripts/work-bundle/dispatcher.py @@ -11,7 +11,7 @@ from legacy import cmd_legacy_command_removed from metadata_profile import cmd_domain_profile from bootstrap_config import cmd_migrate_work_bundle_config -from project import cmd_cleanup_member, cmd_doctor_project, cmd_init_project, cmd_migrate_project, cmd_migrate_to_multi_repository, cmd_project, cmd_provision_member, cmd_register_project_command, cmd_session_start, cmd_set_prefer_subagent, cmd_show_project, cmd_validate_project +from project import cmd_cleanup_member, cmd_doctor_project, cmd_init_project, cmd_migrate_project, cmd_migrate_to_multi_repository, cmd_project, cmd_provision_member, cmd_register_project_command, cmd_session_start, cmd_show_project, cmd_validate_project from rules import cmd_create_rules, cmd_validate_rules from skill_registry import cmd_merge_skill_hints, cmd_registry from stage_events import cmd_stage_events @@ -97,7 +97,7 @@ def _load_evaluation_identity(): 'defer-workspace-member', 'attach-deferred-remote', 'doctor-project', 'provision-member', 'cleanup-member', 'credential-list', 'instruction-audit', 'session-start', 'inspect-project-initialization', - 'validate-project', 'set-prefer-subagent', 'create-rules', 'validate-rules', + 'validate-project', 'create-rules', 'validate-rules', 'defect-ensure-store', 'defect-create-evidence', 'defect-build-index', 'defect-write-index', 'defect-archive-evidence', 'defect-migrate-store', 'validate-contract', 'assert-migration-stop', @@ -207,8 +207,6 @@ def main() -> int: return cmd_project(parsed.args, inspect_only=True, repo_model=True) if command == 'validate-project': return cmd_validate_project(parsed.args) - if command == 'set-prefer-subagent': - return cmd_set_prefer_subagent(parsed.args) if command == 'create-rules': return cmd_create_rules(parsed.args) if command == 'validate-rules': diff --git a/scripts/work-bundle/migration.py b/scripts/work-bundle/migration.py index 220a1e9..8b58cd8 100644 --- a/scripts/work-bundle/migration.py +++ b/scripts/work-bundle/migration.py @@ -405,7 +405,6 @@ def _metadata_text( 'workspace_mode: multi-repository', f'project_root: {_yaml_string(member_root)}', f'industry: {_yaml_string(repository_id)}', - 'prefer_subagent: false', 'metadata_compatibility:', ' readable_versions: [2, 3]', ' migration_requires_explicit_apply: true', diff --git a/scripts/work-bundle/project.py b/scripts/work-bundle/project.py index acbe427..d642ca9 100644 --- a/scripts/work-bundle/project.py +++ b/scripts/work-bundle/project.py @@ -1135,7 +1135,6 @@ def inspect_project(project_root: Path) -> dict: 'global_bootstrap_path': runtime.get('global_bootstrap_path'), 'global_bootstrap_exists': runtime.get('global_bootstrap_exists'), 'resolved_work_bundle_root': runtime.get('resolved_work_bundle_root'), - 'prefer_subagent': resolve_effective_prefer_subagent(project_root), } @@ -1368,17 +1367,6 @@ def _set_yaml_scalar(path: Path, key: str, value: str) -> bool: return changed -def set_prefer_subagent(project_root: Path, scope: str, enabled: bool) -> tuple[Path, bool]: - value = 'true' if enabled else 'false' - if scope == 'global': - path = work_bundle_config_root() / GLOBAL_BOOTSTRAP_FILE_NAME - elif scope == 'project': - path = project_metadata_path(project_root) - else: - raise ValueError(f'unsupported prefer_subagent scope: {scope}') - return path, _set_yaml_scalar(path, 'prefer_subagent', value) - - def _ensure_registry_schema_version(text: str, version: str = REGISTRY_SCHEMA_VERSION) -> str: rendered = text if text.endswith('\n') else text + '\n' for line in rendered.splitlines(): @@ -2575,29 +2563,6 @@ def cmd_migrate_to_multi_repository(args: list[str]) -> int: return 0 -def cmd_set_prefer_subagent(args: list[str]) -> int: - parser = argparse.ArgumentParser(prog="wb.py set-prefer-subagent") - parser.add_argument("value", choices=["true", "false", "enable", "disable", "enabled", "disabled", "on", "off"]) - parser.add_argument("--scope", choices=["global", "project"], required=True) - parser.add_argument("--project-root", default=".") - parsed = parser.parse_args(args) - enabled = parsed.value in {"true", "enable", "enabled", "on"} - project_root = Path(parsed.project_root).expanduser().resolve() - target_path, changed = set_prefer_subagent(project_root, parsed.scope, enabled) - effective = resolve_effective_prefer_subagent(project_root) - out({ - "command": "set-prefer-subagent", - "status": "updated" if changed else "skipped", - "scope": parsed.scope, - "prefer_subagent": enabled, - "target_path": str(target_path), - "project_root": str(project_root), - "effective_prefer_subagent": effective, - "changed_files": [str(target_path)] if changed else [], - }) - return 0 - - def apply_layout_v2_to_v3( project_root: Path, name: str | None = None, diff --git a/skills/orch-create-handoff/SKILL.md b/skills/orch-create-handoff/SKILL.md index 6720e12..9525eff 100644 --- a/skills/orch-create-handoff/SKILL.md +++ b/skills/orch-create-handoff/SKILL.md @@ -54,7 +54,7 @@ Always include identity, related artifacts, result state, and concise summary. I - `task_fit_check` for completed or partial task results, covering the compiled task brief and assigned task. Escalate to full source artifacts only for inconsistent compiled context or a reviewer-reported source-contract problem. - `repository` when repository preflight, accepted baseline, changed paths, or blocker state matters. - `codegraph` when source-code inspection or edits were in scope; keep it to `root`, `applicable`, `up_to_date`, and fallback/blocker facts unless more detail is needed. -- `delegation_evidence` when task, phase, or plan ownership was delegated or fallback proof is required; record `internal_spawn_used_for_task_delegation: false`. +- `delegation_evidence` for task ownership; record only delegated state, `owner_kind: subagent`, minimum agent/run identity, and provider-neutral mechanism. ## Hard Rules @@ -66,7 +66,7 @@ Always include identity, related artifacts, result state, and concise summary. I - Do not include durable-knowledge recommendations, orchestration review recommendations, executor advice fields, or strategy advice in executor-result handoffs. - Stop if source artifact paths or current state are unknown. - Executor-result handoffs must list changed files or inspected artifacts, validation, unresolved blockers when present, and compact `task_fit_check` when applicable. -- Executor-result handoffs must not omit applicable `codegraph:` or `delegation_evidence:` and must not record contradictory delegation evidence such as `internal_spawn_used_for_task_delegation: true`. +- Executor-result handoffs must not omit applicable `codegraph:` or task `delegation_evidence:` and must not include UI, visibility, fallback, or controller-ownership fields. ## Status and Index @@ -87,7 +87,7 @@ Load only when creating or validating: ## Validation -Confirm required sparse YAML metadata exists, referenced specs/plans/phases/tasks/files are listed when applicable, unresolved blockers are explicit when present, executor-result fields are complete by applicability rather than fixed section presence, forbidden executor advice fields are absent, applicable `codegraph:` evidence includes compact up-to-date or fallback/blocker facts, delegated executor-result handoffs include `delegation_evidence:`, `visible_reference` when available, and `internal_spawn_used_for_task_delegation: false`, raw chat is excluded, no handoff is written under `.work-bundle/knowledge/`, no active orchestration handoff is created, and execution-completion handoffs did not invoke retrieval. +Confirm required sparse YAML metadata exists, referenced specs/plans/phases/tasks/files are listed when applicable, unresolved blockers are explicit when present, executor-result fields are complete by applicability rather than fixed section presence, forbidden executor advice fields are absent, applicable `codegraph:` evidence includes compact up-to-date or fallback/blocker facts, task executor-result handoffs include neutral `delegation_evidence` with agent/run identity and mechanism, raw chat is excluded, no handoff is written under `.work-bundle/knowledge/`, no active orchestration handoff is created, and execution-completion handoffs did not invoke retrieval. ## Runtime Rules diff --git a/skills/orch-doctor/SKILL.md b/skills/orch-doctor/SKILL.md index 299f89c..0985f2b 100644 --- a/skills/orch-doctor/SKILL.md +++ b/skills/orch-doctor/SKILL.md @@ -55,14 +55,14 @@ Verify: 11. orchestration evals that require v3 role labels are backed by orch skill documentation; 12. specification and plan contracts allocate `dev-semantic-convergence`, caller-specific lenses, compact `semantic_loop` evidence, and the body-level `Quality gate: verified|blocked` result where applicable; 13. task contracts carry source IDs, methodology, provider-neutral capability, compiled-brief context, and acceptance-review fields; -14. execution contracts preserve no-retrieval execution, compile bounded task/review packets, require fresh task validation plus acceptance review, and keep reduced-independence fallback explicit; +14. execution contracts preserve no-retrieval execution, compile bounded task/review packets, require mandatory subagent task ownership, and require fresh task validation plus acceptance review; 15. the CodeGraph-first rule remains conditional on an indexed target and requires a recorded fallback reason when unavailable; 16. active orchestration contracts do not depend on `HABITS.md` or the deprecated role-selection subsystem. 17. executor-result contracts default to sparse YAML, require fields by applicability, reject forbidden executor advice fields, and do not require active orchestration handoffs; 18. compact CodeGraph evidence retains `root`, `applicable`, `up_to_date`, and required fallback or blocker facts, including `no-index` and `sync-failed` where applicable; -19. compact visible delegation evidence uses `delegation_evidence`, `visible_reference` when available, and `internal_spawn_used_for_task_delegation: false`; -20. no active orch contract reintroduces invisible internal spawn work as a valid task-delegation vehicle; -21. handoff and review contracts validate compact CodeGraph and visible delegation outcomes by applicability rather than fixed prose sections. +19. compact neutral ownership evidence uses `delegation_evidence` with only delegated state, subagent owner kind, agent/run identity, and provider-neutral mechanism; +20. no active orch contract permits controller or single-agent task mutation; +21. handoff and review contracts validate compact CodeGraph and neutral ownership outcomes by applicability rather than fixed prose sections. ## Workflow Integrity Checks @@ -70,9 +70,9 @@ Verify: - `orch-create-specification`, `orch-create-implementation-plan`, `orch-create-handoff`, `orch-execute-plan`, `orch-review-plan`, and `orch-doctor` keep distinct responsibilities; - artifact creation modes do not execute implementation work; -- `orch-execute-plan` checks sub-agent support before delegation; -- `orch-execute-plan` preserves the single-agent fallback and does not fail only because sub-agents are unavailable; -- layered `prefer_subagent` remains permission-only and cannot bypass preflight, dependency, scope, or handoff safety checks; +- `orch-execute-plan` requires subagent support before any implementation or repair mutation and fails closed when unavailable; +- the orchestration thread schedules, compiles, coordinates, validates, reviews, and manages lifecycle without mutating task write scope; +- planner-proven independent disjoint tasks dispatch before waiting while dependent, overlapping, and same-workspace tasks serialize; - executor-result handoffs require local task-fit evidence against the compiled brief and assigned task, with full lifecycle artifacts reserved for inconsistent context or source-contract escalation; - executor-result handoffs default to sparse YAML, omit non-applicable fields, reject forbidden executor advice fields, and require compact `codegraph:` and `delegation_evidence:` only when applicable; - active orchestration handoffs are unavailable and continuation uses active specs, plans, tasks, indexes, and executor-result handoffs; @@ -85,13 +85,13 @@ Verify: Look for one-sided or conflicting instructions that would bias execution toward a single path when alternatives are required: -- sub-agent scheduler must not be mandatory when sub-agents are unavailable or unsafe; -- single-agent fallback must not silently bypass required handoffs or status updates; +- mandatory subagent ownership must fail closed before mutation when no safe subagent path is available; +- controller scheduling must not become controller implementation or repair; - review must not be treated as execution; - execution completion must not imply archival; - durable knowledge extraction must not be implied by executor-result handoffs; - CodeGraph sync evidence must not be optional when `.codegraph/` exists and graph-derived source work is in scope; -- visible delegation wording must not allow invisible internal spawn work to own delegated plan, phase, or task execution. +- ownership wording must remain provider-neutral and must not depend on UI visibility. Deterministic doctor checks are limited to bounded file presence, JSON shape, required contract terms, and forbidden active dependencies. They must not judge @@ -119,7 +119,7 @@ Files changed: none ## Validation -Confirm `dev-rules-doctor` was used first, diagnostics stayed read-only, orch skill coverage was checked, skill front matter was checked, semantic-convergence and compiled-context terms were present, sparse YAML and applicability terms were present, forbidden executor advice fields and active orchestration handoffs were rejected, compact CodeGraph and visible delegation safety terms were present, invisible internal spawn task-delegation regressions were absent, forbidden active dependencies were absent, workflow responsibilities remained distinct, required fallback paths were present, archival remained isolated to `orch-review-plan`, and no files were changed. +Confirm `dev-rules-doctor` was used first, diagnostics stayed read-only, orch skill coverage was checked, skill front matter was checked, semantic-convergence and compiled-context terms were present, sparse YAML and applicability terms were present, forbidden executor advice fields and active orchestration handoffs were rejected, compact CodeGraph and neutral subagent ownership terms were present, controller mutation and single-agent fallback regressions were absent, forbidden active dependencies were absent, workflow responsibilities remained distinct, fail-closed paths were present, archival remained isolated to `orch-review-plan`, and no files were changed. ## Runtime Rules @@ -137,12 +137,12 @@ Diagnose develop-rules installation health and orchestrator workflow consistency - Stop and report the blocker if `dev-rules-doctor` cannot run; do not treat installation health as passed. - Perform a read-only orchestrator audit across orchestrator skill files, workflow reference, orchestration evals, and helper commands in `scripts/orch.py`. - Verify skill coverage, front matter consistency, workflow responsibility separation, retrieval-policy mappings, helper command availability or declared fallback behavior, and required execution fallback paths. -- Verify `orch-execute-plan` compiles bounded task/review packets, preserves visible sub-agent and reduced-independence fallback paths, requires fresh validation plus task acceptance, and does not archive artifacts during execution. +- Verify `orch-execute-plan` compiles bounded task/review packets, requires mandatory subagent ownership for implementation and repair, fails closed before mutation when unavailable, requires fresh validation plus task acceptance, and does not archive artifacts during execution. - Verify `orch-review-plan` is the only skill that archives completed specification, plan, and handoff artifacts. - Verify knowledge-using orch skills route through `keep-summarizing` rather than direct `.work-bundle/knowledge/` browsing. - Verify executor-result contracts default to sparse YAML, require fields by applicability, omit non-applicable fields, reject forbidden executor advice fields, and do not require active orchestration handoffs. - Verify compact CodeGraph evidence includes `root`, `applicable`, `up_to_date`, and accepted fallback or blocker facts such as `no-index` and `sync-failed` where applicable. -- Verify compact visible delegation evidence uses `delegation_evidence`, `visible_reference` when available, and `internal_spawn_used_for_task_delegation: false`, and does not permit invisible internal spawn work to own delegated task execution. +- Verify compact neutral ownership evidence uses `delegation_evidence` with minimum agent/run identity and mechanism, rejects UI/visibility fields, and does not permit controller-owned task execution. - Look for workflow bias such as false review independence, skipped handoffs, scheduler-owned code review, mandatory full lifecycle context for a valid brief, or handoff conclusions treated as persisted knowledge. - Report findings as concrete repair actions with cited conflicting artifacts when issues are found. - Emit doctor output with `Files changed: none`. diff --git a/skills/orch-execute-plan/SKILL.md b/skills/orch-execute-plan/SKILL.md index 2362f54..85037bc 100644 --- a/skills/orch-execute-plan/SKILL.md +++ b/skills/orch-execute-plan/SKILL.md @@ -1,6 +1,6 @@ --- name: orch-execute-plan -description: 'Execute a WorkBundle task, phase, or plan through compiled task briefs, task-local methodology, optional independent acceptance review, and dependency-aware scheduling.' +description: 'Execute a WorkBundle task, phase, or plan through mandatory subagent task ownership, compiled task briefs, task-local methodology, optional independent acceptance review, and dependency-aware scheduling.' --- # orch-execute-plan @@ -23,9 +23,9 @@ python3 scripts/orch.py build-task-brief --task ``` Missing source IDs; decision authority other than `none-relevant` or an `AUTH-NNN` alias whose carried constraint was reconciled in the verified specification; `conflict_status: escalate`; inconsistent scope; or unsafe workspace state fails closed with the existing typed blocker. Truth Basis conflict uses `decision-blocked`. The compiled brief includes `AUTH-NNN: `, not the alias alone. -7. Choose the provider-neutral capability from the task profile. Partition only independent tasks with disjoint write scopes. Contract-decoupled participants validate against the common contract, accepted prior handoffs, and task-local files; they reach the named barrier before convergence work. -8. When user/environment policy permits delegation, use visible multi-agent subagents for task ownership. Invisible helper workers may support bounded analysis only. If independent subagents are unavailable, use single-agent execution and mark later review `reviewer_independent: false`. -9. Always validate the executor-result with the shared helper. For a mapped capability task, report each validation under its compiled evidence ID and invariant IDs, and add `evidence_closure` using the allocated boundary, freshness, and evidence IDs. The helper observes required process/inspection items in the bound worktree as one Git-state-neutral batch, reuses the compiled identities, and rejects missing, incapable, contradictory, stale, wrong-boundary, failed, or unexecuted evidence before authorizing from post-execution task-caused delta. Executor closure claims are corroboration, not harness proof. Compile `build-review-package` and assign `dev-code-review` only when `acceptance_review.required: true` or compiled `review_required: true`. The scheduler does not perform code-quality review. +7. Choose the provider-neutral capability from the task profile. Consume planner-proven dependencies, write scopes, common-contract groups, barriers, convergence ownership, and isolation requirements. Dispatch every ready independent task with disjoint write scope to a separate execution workspace before awaiting any result. Serialize dependent, overlapping, or same-workspace mutation. Contract-decoupled participants validate against the common contract, accepted prior handoffs, and task-local files; they reach the named barrier before convergence work. +8. Selecting `orch-execute-plan` makes every implementation and repair task subagent-owned. Before any task mutation, confirm a host-native or Execution-Flow-routed subagent is available and bind the task to its neutral agent/run identity. If no subagent is available, fail closed with `workspace-blocked`; there is no controller or single-agent fallback. Do not substitute `reviewer_independent: false` for a missing task owner. The orchestration thread may schedule, compile briefs, coordinate barriers, validate results, route reviews, and manage lifecycle, but it must not implement or repair task write scope. +9. Always validate neutral `delegation_evidence` and reject acceptance when mutation provenance shows the controller changed task-owned write scope, even if validation is green. Then validate the executor-result with the shared helper. For a mapped capability task, report each validation under its compiled evidence ID and invariant IDs, and add `evidence_closure` using the allocated boundary, freshness, and evidence IDs. The helper observes required process/inspection items in the bound worktree as one Git-state-neutral batch, reuses the compiled identities, and rejects missing, incapable, contradictory, stale, wrong-boundary, failed, or unexecuted evidence before authorizing from post-execution task-caused delta. Executor closure claims are corroboration, not harness proof. Compile `build-review-package` and assign `dev-code-review` only when `acceptance_review.required: true` or compiled `review_required: true`. The scheduler does not perform code-quality review. ```bash python3 scripts/orch.py validate-executor-result --task --handoff @@ -34,6 +34,7 @@ python3 scripts/orch.py validate-executor-result --task --handoff --repository-id --remote --name --path --default-branch (--dry-run|--accepted-proposal-id --apply)` | | Provision member | `provision-member --workspace-root [--workspace-slug ] --origin --repository-id --working-branch --base-ref [--dry-run|--apply]` | | Cleanup member | `cleanup-member --workspace-root --repository-id (--dry-run|--apply)` | -| Set sub-agent preference | `set-prefer-subagent --scope [--project-root ]` | `initialize-project` remains a compatibility alias for `init-project`; prefer `init-project` in new instructions. -The explicit `--workspace-root` and `--project-root` selectors remain available only on commands whose live help lists them, such as `show-project` and project-scoped `set-prefer-subagent`. New creation must reject a missing or contradictory mode/root combination rather than silently infer topology. Single-repository mode is current and fully supported, not legacy or transitional. +The explicit `--workspace-root` and `--project-root` selectors remain available only on commands whose live help lists them, such as `show-project`. New creation must reject a missing or contradictory mode/root combination rather than silently infer topology. Single-repository mode is current and fully supported, not legacy or transitional. **Portable v4 migration guardrails:** before `migrate-control-plane`, load every applicable rule body in full, including project context, registry authority, lifecycle, repository boundary, security exclusion, and defect routing. Do not sample those rules by keyword. Resolve canonical remotes from explicit `--repository-remote` input, registry locator authority, and the live origin chain. When authoritative network remotes conflict, stop and ask the user which remote is canonical; rerun the exact dry-run with `--repository-remote` after the decision. During this workflow, do not edit the project registry directly and do not change an external repository's Git config. `show-project`, `validate-project`, and `doctor-project` route metadata-version-4 workspaces to v4 control-plane validation; repair must never rewrite portable v4 metadata into v3 shape. @@ -79,20 +76,6 @@ An exact workspace-local checkout created by an older WorkBundle version may hav **`init-project --dry-run` / `validate-project --dry-run`:** inspect and report mechanical failures without writing project files. -**`prefer_subagent` management:** - -- `prefer_subagent` is a boolean preference only; it does not bypass orchestration preflight, dependency, write-scope, handoff, or fallback rules. -- Effective value resolves as project metadata first, then global bootstrap, then `false`: - - project: `$workspace_root/.work-bundle/project.yaml` -> `prefer_subagent`; - - global: `$work_bundle_config_root/bootstrap.yaml` -> `prefer_subagent`; - - default: `false`. -- Use `set-prefer-subagent true --scope global` for requests such as "Enable global `prefer_subagent`". -- Use `set-prefer-subagent false --scope global` for requests such as "Disable global `prefer_subagent`". -- Use `set-prefer-subagent false --scope project --project-root ` for requests such as "Disable `prefer_subagent` for current workspace". -- Use `set-prefer-subagent true --scope project --project-root ` for requests such as "Enable `prefer_subagent` for current workspace". -- The command updates only the selected YAML file and reports `target_path`, `changed_files`, and `effective_prefer_subagent` in JSON. -- Do not hand-edit either YAML file for this preference when the dispatcher command is available. - **Registry and slug (per `wb-project-registry`):** - Resolve the project registry path from `bootstrap.yaml` field `project_registry`. @@ -137,7 +120,6 @@ An exact workspace-local checkout created by an older WorkBundle version may hav - In multi-repository mode place runtime Git control stores beneath `$workspace_root/.work-bundle/git/` and exclude them from workspace-management commits and broad scans. - Render `.work-bundle/project.yaml` from `references/assets/template/project.yaml`. - Render `.work-bundle/project.yaml` with metadata v3 workspace/member state from mechanical Git and per-member `.codegraph/` inspection. -- Render `.work-bundle/project.yaml` with a `prefer_subagent: false` default unless a future template version explicitly changes the default. - Render `.work-bundle/project.yaml` with an `agents_sync` section that owns WorkBundle `AGENTS.md` checksum and sync-status state. - Create, append, or refresh `AGENTS.md` with the WorkBundle managed section from `references/assets/template/AGENTS.md`; do not overwrite user-authored content outside the managed section. - Create or preserve required `.gitignore` entries. @@ -193,7 +175,6 @@ Use `migrate-project` only for unambiguous single-repository legacy layout upgra - Initialized, doctored, validated, registered, or migrated project workspace. - Updated bootstrap-resolved project-registry locator or metadata-v4 device binding when its lifecycle command runs. - JSON command output with `status`, `failures`, registry status, metadata version/mode/resources/member evidence, redacted lifecycle transaction state, AGENTS sync evidence, and changed files where applicable. Compatibility reads retain existing v2 evidence fields until explicit migration. -- For `set-prefer-subagent`, JSON command output with `status`, `scope`, `prefer_subagent`, `target_path`, `changed_files`, and `effective_prefer_subagent`. - Migration report and optional legacy-bootstrap archive paths under `.work-bundle/orchestration/docs/` when migration retires legacy artifacts. ## On Failure diff --git a/tests/test_control_plane_v4.py b/tests/test_control_plane_v4.py index 378da49..8e5b3ba 100644 --- a/tests/test_control_plane_v4.py +++ b/tests/test_control_plane_v4.py @@ -13,7 +13,12 @@ REPO_ROOT = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(REPO_ROOT / "scripts/work-bundle")) +WORK_BUNDLE_SCRIPTS = REPO_ROOT / "scripts/work-bundle" +loaded_core = sys.modules.get("core") +loaded_core_path = Path(getattr(loaded_core, "__file__", "")) if loaded_core is not None else None +if loaded_core_path is not None and WORK_BUNDLE_SCRIPTS not in loaded_core_path.parents: + sys.modules.pop("core", None) +sys.path.insert(0, str(WORK_BUNDLE_SCRIPTS)) from workspace_resources import CREDENTIAL_TEMPLATE, SCRIPT_INDEX_TEMPLATE from control_plane import ( ControlPlaneError, @@ -66,7 +71,6 @@ def config_root(tmp_path: Path) -> Path: f"work_bundle_root: {REPO_ROOT}", 'project_registry: "$work_bundle_config_root/registry/projects.yaml"', 'skill_registry: "$work_bundle_config_root/registry/skill-registry.yaml"', - "prefer_subagent: false", "", ] ), @@ -269,7 +273,7 @@ def test_v3_to_v4_migration_is_deterministic_and_splits_local_state(tmp_path: Pa assert "workspace:\n id: wb-" in metadata assert f"canonical: {remote}" in metadata assert "custom_portable:" in metadata - for forbidden in ("workspace_root:", "project_root:", "observed_head:", "observation_time:", "git_control_root:"): + for forbidden in ("workspace_root:", "project_root:", "observed_head:", "observation_time:", "git_control_root:", "prefer_subagent:"): assert forbidden not in metadata registry = (config / "registry/projects.yaml").read_text(encoding="utf-8") assert str(workspace) in registry @@ -1383,8 +1387,8 @@ def test_v4_schema_rejects_duplicate_repository_ids_and_invalid_mode(tmp_path: P assert run_wb(config, "init-workspace", str(workspace), "--slug", "demo", "--repository", f"source-main={remote}", "--apply").returncode == 0 metadata = workspace / ".work-bundle/project.yaml" text = metadata.read_text(encoding="utf-8") - duplicate = text[text.index(" - id: source-main"):text.index("prefer_subagent:")] - metadata.write_text(text.replace(" mode: multi-repository", " mode: invalid").replace("prefer_subagent:", duplicate + "prefer_subagent:"), encoding="utf-8") + duplicate = text[text.index(" - id: source-main"):text.index("agents_sync:")] + metadata.write_text(text.replace(" mode: multi-repository", " mode: invalid").replace("agents_sync:", duplicate + "agents_sync:", 1), encoding="utf-8") doctor = run_wb(config, "doctor-workspace", str(workspace)) failures = json.loads(doctor.stdout)["portable"]["failures"] assert "WB_CONTROL_PLANE_WORKSPACE_MODE_INVALID" in failures @@ -1765,7 +1769,7 @@ def write_composite_metadata(workspace: Path, *, include_root: bool = True, memb "", ] ) - text = text.replace("prefer_subagent:", member + "prefer_subagent:") + text = text.replace("agents_sync:", member + "agents_sync:", 1) metadata.write_text(text, encoding="utf-8") @@ -1933,7 +1937,7 @@ def test_v4_composite_schema_rejects_duplicate_member_paths(self) -> None: ] ) metadata.write_text( - metadata.read_text(encoding="utf-8").replace("prefer_subagent:", extra + "prefer_subagent:"), + metadata.read_text(encoding="utf-8").replace("agents_sync:", extra + "agents_sync:", 1), encoding="utf-8", ) doctor = run_wb(config, "doctor-workspace", str(workspace)) @@ -1945,7 +1949,7 @@ def test_v4_existing_modes_still_reject_crossed_bindings(self) -> None: metadata = workspace / ".work-bundle/project.yaml" text = metadata.read_text(encoding="utf-8") metadata.write_text( - text.replace("prefer_subagent:", "\n".join([ + text.replace("agents_sync:", "\n".join([ " - id: extra-member", " role: source", " remote:", @@ -1958,8 +1962,8 @@ def test_v4_existing_modes_still_reject_crossed_bindings(self) -> None: " materialization:", " required: true", " operation_policy: inherit", - "prefer_subagent:", - ])), + "agents_sync:", + ]), 1), encoding="utf-8", ) doctor = run_wb(config, "doctor-workspace", str(workspace)) diff --git a/tests/test_multi_repository_member.py b/tests/test_multi_repository_member.py index 0e38da9..f0fd544 100644 --- a/tests/test_multi_repository_member.py +++ b/tests/test_multi_repository_member.py @@ -231,7 +231,7 @@ def test_multi_member_add_preserves_mode_and_replays_without_root_git(multi, ado registry = config / "registry/projects.yaml" original = metadata.read_text() # Extra top-level fields must not capture the appended repository block. - original = original.replace("prefer_subagent: false\n", "") + "custom_owner_field: retained\n" + original = original + "custom_owner_field: retained\n" metadata.write_text(original) before = metadata.read_bytes(), registry.read_bytes() proposal = propose(config, workspace, remote) @@ -399,9 +399,9 @@ def test_member_add_preserves_yaml_section_boundaries(multi, tmp_path, monkeypat elif shape == "anchored-header": text = text.replace("source_repositories:", "source_repositories: &sources") elif shape == "quoted-key": - text = text.replace("prefer_subagent:", "'custom_owner_field': retained\nprefer_subagent:") + text = text.replace("agents_sync:", "'custom_owner_field': retained\nagents_sync:", 1) elif shape == "quoted-nested": - text = text.replace("prefer_subagent:", "'owner_settings':\n contact:\n team: engineering\nprefer_subagent:") + text = text.replace("agents_sync:", "'owner_settings':\n contact:\n team: engineering\nagents_sync:", 1) elif shape == "commented-control": text = text.replace("control_plane:", "control_plane: # portable settings") elif shape == "owner-flow": diff --git a/tests/test_orchestration_skill_rule_boundary.py b/tests/test_orchestration_skill_rule_boundary.py index cbd42dd..f6e34b4 100644 --- a/tests/test_orchestration_skill_rule_boundary.py +++ b/tests/test_orchestration_skill_rule_boundary.py @@ -122,7 +122,9 @@ def test_execute_skill_uses_compiler_independent_review_and_typed_blockers() -> "build-review-package", "dev-code-review", "The scheduler does not perform code-quality review", - "reviewer_independent: false", + "every implementation and repair task subagent-owned", + "there is no controller or single-agent fallback", + "must not implement or repair task write scope", "After two failed repair rounds", "acceptance_review.required: true", "review_required: true", @@ -144,6 +146,19 @@ def test_execute_skill_uses_compiler_independent_review_and_typed_blockers() -> assert token in text +def test_task_ownership_contract_is_provider_neutral_and_not_visibility_specific() -> None: + for relative in [ + "skills/orch-create-handoff/SKILL.md", + "rules/orchestration/orch-handoff-required.md", + "references/assets/orchestration/contract/handoff-executor-result-v1.md", + ]: + text = read(relative) + for token in ["delegation_evidence", "owner_kind", "agent", "run", "mechanism"]: + assert token in text, f"{relative}: {token}" + for retired in ["visible_reference", "internal_spawn_used_for_task_delegation", "single-agent-fallback"]: + assert retired not in text, f"{relative}: {retired}" + + def test_final_review_is_workflow_audit_not_code_review() -> None: text = read("skills/orch-review-plan/SKILL.md") for token in [ diff --git a/tests/test_orchestration_workflow_contracts.py b/tests/test_orchestration_workflow_contracts.py index 3e90f00..26f61e8 100644 --- a/tests/test_orchestration_workflow_contracts.py +++ b/tests/test_orchestration_workflow_contracts.py @@ -869,7 +869,9 @@ def test_workflow_assigns_review_ownership_and_repair_loop() -> None: "Reviewers own acceptance judgment", "Schedulers own dependencies", "they do not perform code-quality review", - "reviewer_independent: false", + "requires a subagent owner for every task", + "fails closed before task mutation", + "dispatch before any wait", "After two failed low-cost repair rounds", "A task becomes `Completed` only when", "`Completed` does not require `verdict: accept` unless review was required", @@ -1785,9 +1787,11 @@ def test_overlapping_writes_are_not_parallelizable() -> None: create = read("skills/orch-create-implementation-plan/SKILL.md") workflow = read("references/assets/orchestration/workflow.md") plan = read("references/assets/orchestration/contract/plan-v1.md") - assert "Partition only independent tasks with disjoint write scopes" in execute + assert "planner-proven dependencies, write scopes" in execute + assert "Dispatch every ready independent task with disjoint write scope" in execute + assert "Serialize dependent, overlapping, or same-workspace mutation" in execute assert "disjoint write scopes" in create - assert "write scopes are disjoint" in workflow + assert "Independent disjoint tasks in distinct execution workspaces dispatch before any wait" in workflow assert "disjoint write scopes" in workflow assert "assign parallel tasks only when dependencies are satisfied and write scopes are disjoint" in plan assert "unsafe parallelization is explicitly blocked by dependency or scope evidence" in plan diff --git a/tests/test_project_initialization.py b/tests/test_project_initialization.py index bd850ec..5181ae0 100644 --- a/tests/test_project_initialization.py +++ b/tests/test_project_initialization.py @@ -85,7 +85,6 @@ def bootstrap_config(tmp_path: Path, work_bundle_root: Path | None = None) -> Pa f"work_bundle_root: {work_bundle_root or REPO_ROOT}", 'project_registry: "$work_bundle_config_root/registry/projects.yaml"', 'skill_registry: "$work_bundle_config_root/registry/skill-registry.yaml"', - "prefer_subagent: false", "", ] ), @@ -1050,8 +1049,7 @@ def test_validate_project_omits_pointer_diagnostics(tmp_path: Path) -> None: assert result.returncode == 0, result.stdout + result.stderr data = json.loads(result.stdout) assert data["path_model"]["work_bundle_root"] - assert data["prefer_subagent"]["prefer_subagent"] is False - assert data["prefer_subagent"]["source"] == "project" + assert "prefer_subagent" not in data for key in ( "work_bundle_root_pointer_path", "work_bundle_root_pointer_exists", @@ -1140,19 +1138,17 @@ def test_migrate_work_bundle_config_migrates_legacy_bootstrap(tmp_path: Path) -> assert Path(data["retired_root_pointer"]).is_file() -def test_templates_include_layered_prefer_subagent_defaults() -> None: +def test_templates_omit_retired_subagent_preference() -> None: bootstrap_text = (REPO_ROOT / "references/assets/template/bootstrap.yaml").read_text(encoding="utf-8") project_text = (REPO_ROOT / "references/assets/template/project.yaml").read_text(encoding="utf-8") agents_text = (REPO_ROOT / "references/assets/template/AGENTS.md").read_text(encoding="utf-8") - assert "prefer_subagent: false" in bootstrap_text - assert "prefer_subagent: false" in project_text + assert "prefer_subagent" not in bootstrap_text + assert "prefer_subagent" not in project_text assert "agents_sync:" in project_text assert "template_checksum_sha256: \"\"" in project_text assert "status: never-synced" in project_text - assert ".work-bundle/project.yaml` -> `prefer_subagent`" in agents_text - assert "then `$work_bundle_config_root/bootstrap.yaml` -> `prefer_subagent`, then `false`" in agents_text - assert "bypass repository preflight" in agents_text + assert "prefer_subagent" not in agents_text def test_project_metadata_v3_records_git_and_codegraph_state(tmp_path: Path) -> None: @@ -1879,7 +1875,7 @@ def test_provision_member_resumes_matching_verified_checkout(tmp_path: Path, mon assert json.loads(resumed.stdout)["transaction"]["state"] == "published" -def test_resolve_effective_prefer_subagent_uses_project_global_default_order(tmp_path: Path, monkeypatch) -> None: +def test_runtime_has_no_subagent_preference_resolver(tmp_path: Path, monkeypatch) -> None: script_root = REPO_ROOT / "scripts" / "work-bundle" module = sys.modules.get("core") module_file = Path(getattr(module, "__file__", "")) if module is not None else None @@ -1889,27 +1885,8 @@ def test_resolve_effective_prefer_subagent_uses_project_global_default_order(tmp try: import core # type: ignore[import-not-found] - config_root = tmp_path / "config" - config_root.mkdir() - monkeypatch.setenv("WB_CONFIG_ROOT", str(config_root)) - project_root = tmp_path / "project" - metadata_path = project_root / ".work-bundle/project.yaml" - metadata_path.parent.mkdir(parents=True) - - assert core.resolve_effective_prefer_subagent(project_root)["prefer_subagent"] is False - assert core.resolve_effective_prefer_subagent(project_root)["source"] == "default" - - (config_root / "bootstrap.yaml").write_text("prefer_subagent: true\n", encoding="utf-8") - assert core.resolve_effective_prefer_subagent(project_root)["prefer_subagent"] is True - assert core.resolve_effective_prefer_subagent(project_root)["source"] == "global" - - metadata_path.write_text("prefer_subagent: false\n", encoding="utf-8") - resolved = core.resolve_effective_prefer_subagent(project_root) - assert resolved["prefer_subagent"] is False - assert resolved["source"] == "project" - - metadata_path.write_text("prefer_subagent: true\n", encoding="utf-8") - assert core.resolve_effective_prefer_subagent(project_root)["prefer_subagent"] is True + assert not hasattr(core, "resolve_effective_prefer_subagent") + assert "prefer_subagent" not in core.resolve_bootstrap_runtime() finally: if sys.path and sys.path[0] == str(REPO_ROOT / "scripts" / "work-bundle"): sys.path.pop(0) @@ -2057,46 +2034,22 @@ def test_init_project_metadata_records_agents_sync_checksum(tmp_path: Path) -> N assert json.loads(rerun.stdout)["changed_files"] == [] -def test_set_prefer_subagent_updates_global_bootstrap(tmp_path: Path) -> None: +def test_retired_subagent_preference_command_is_not_exposed(tmp_path: Path) -> None: config_root, project = _init_fixture_project(tmp_path) result = run_wb(config_root, "set-prefer-subagent", "enable", "--scope", "global", "--project-root", str(project)) - assert result.returncode == 0, result.stdout + result.stderr - data = json.loads(result.stdout) - - assert data["command"] == "set-prefer-subagent" - assert data["scope"] == "global" - assert data["prefer_subagent"] is True - assert data["status"] == "updated" - assert data["target_path"] == str((config_root / "bootstrap.yaml").resolve()) - assert "prefer_subagent: true" in (config_root / "bootstrap.yaml").read_text(encoding="utf-8") - assert data["effective_prefer_subagent"]["prefer_subagent"] is False - assert data["effective_prefer_subagent"]["source"] == "project" + assert result.returncode == 2 + assert "unknown command" in result.stderr - project_text = (project / ".work-bundle/project.yaml").read_text(encoding="utf-8") - assert "prefer_subagent: false" in project_text - -def test_set_prefer_subagent_updates_current_workspace_override(tmp_path: Path) -> None: +def test_legacy_subagent_preference_input_does_not_appear_in_show_output(tmp_path: Path) -> None: config_root, project = _init_fixture_project(tmp_path) - assert run_wb(config_root, "set-prefer-subagent", "enable", "--scope", "global", "--project-root", str(project)).returncode == 0 - - result = run_wb(config_root, "set-prefer-subagent", "disable", "--scope", "project", "--project-root", str(project)) - assert result.returncode == 0, result.stdout + result.stderr - data = json.loads(result.stdout) - - assert data["scope"] == "project" - assert data["prefer_subagent"] is False - assert data["target_path"] == str((project / ".work-bundle/project.yaml").resolve()) - assert data["effective_prefer_subagent"]["prefer_subagent"] is False - assert data["effective_prefer_subagent"]["source"] == "project" - assert "prefer_subagent: false" in (project / ".work-bundle/project.yaml").read_text(encoding="utf-8") - + metadata = project / ".work-bundle/project.yaml" + metadata.write_text(metadata.read_text(encoding="utf-8") + "prefer_subagent: true\n", encoding="utf-8") show = run_wb(config_root, "show-project", "--project-root", str(project)) assert show.returncode == 0, show.stdout + show.stderr show_data = json.loads(show.stdout) - assert show_data["prefer_subagent"]["global_prefer_subagent"] is True - assert show_data["prefer_subagent"]["project_prefer_subagent"] is False + assert "prefer_subagent" not in show_data def test_migrate_work_bundle_config_resolves_legacy_pointer_without_toolkit_flag(tmp_path: Path) -> None: diff --git a/tests/test_registry_layout_migration.py b/tests/test_registry_layout_migration.py index 425766a..acc9f18 100644 --- a/tests/test_registry_layout_migration.py +++ b/tests/test_registry_layout_migration.py @@ -29,6 +29,20 @@ ) +def restore_work_bundle_import_boundary() -> None: + """Keep direct migration calls isolated from orchestration's `core` module.""" + + if _WB_SCRIPTS in sys.path: + sys.path.remove(_WB_SCRIPTS) + sys.path.insert(0, _WB_SCRIPTS) + core_module = sys.modules.get("core") + core_path = Path(getattr(core_module, "__file__", "")) if core_module is not None else None + if core_path is not None and Path(_WB_SCRIPTS) not in core_path.parents: + sys.modules.pop("core", None) + for name in ("project", "bootstrap_config"): + sys.modules.pop(name, None) + + def run_wb(config_root: Path, *args: str) -> subprocess.CompletedProcess[str]: env = os.environ.copy() env.update( @@ -76,7 +90,6 @@ def bootstrap_config(tmp_path: Path) -> Path: f"work_bundle_root: {REPO_ROOT}", 'project_registry: "$work_bundle_config_root/registry/projects.yaml"', 'skill_registry: "$work_bundle_config_root/registry/skill-registry.yaml"', - "prefer_subagent: false", "", ] ), @@ -471,6 +484,7 @@ def test_blocked_multi_repository_v2(tmp_path: Path) -> None: def test_validation_failure_after_transformation_restores_state(tmp_path: Path, monkeypatch) -> None: + restore_work_bundle_import_boundary() config = bootstrap_config(tmp_path) workspace, remote, _, repo_id = prepare_workspace(tmp_path, "validate-fail", "v3-single.yaml") registry = write_registry(config, [project_block("validate-fail", workspace, repo_id, str(remote))]) @@ -503,6 +517,7 @@ def failing_validate(root: Path, version: str) -> list[str]: def test_intermediate_step_failure_restores_pre_migration_state(tmp_path: Path, monkeypatch) -> None: + restore_work_bundle_import_boundary() config = bootstrap_config(tmp_path) workspace, remote, _, repo_id = prepare_workspace(tmp_path, "mid-fail", "v2-project.yaml") registry = write_registry(config, [project_block("mid-fail", workspace, repo_id, str(remote))]) @@ -541,6 +556,7 @@ def fail_v4(step, root, entry): def test_failed_migration_preserves_symlink_and_nested_credentials( tmp_path: Path, monkeypatch ) -> None: + restore_work_bundle_import_boundary() config = bootstrap_config(tmp_path) workspace, remote, _, repo_id = prepare_workspace(tmp_path, "user-data", "v3-single.yaml") registry = write_registry(config, [project_block("user-data", workspace, repo_id, str(remote))]) diff --git a/tests/test_wor108_subagent_ownership.py b/tests/test_wor108_subagent_ownership.py new file mode 100644 index 0000000..e858d36 --- /dev/null +++ b/tests/test_wor108_subagent_ownership.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +from pathlib import Path +import sys + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts" / "orchestration")) + +from task_ownership import ( # type: ignore[import-not-found] + OwnershipBlocker, + TaskCandidate, + dispatch_ready_wave, + normalize_subagent_provenance, + validate_task_acceptance_ownership, +) + + +def provenance(*, mechanism: str = "host-native") -> dict[str, object]: + return { + "delegated": True, + "owner_kind": "subagent", + "agent_id": "agent-task-001", + "run_id": "run-task-001-a", + "mechanism": mechanism, + } + + +def test_sg01_execute_plan_requires_implicit_subagent_ownership() -> None: + assert normalize_subagent_provenance(provenance())["owner_kind"] == "subagent" + with pytest.raises(OwnershipBlocker, match="workspace-blocked") as error: + normalize_subagent_provenance(None) + assert error.value.code == "workspace-blocked" + + +def test_sg02_no_subagent_fails_closed_before_mutation() -> None: + mutations: list[str] = [] + + with pytest.raises(OwnershipBlocker, match="workspace-blocked"): + dispatch_ready_wave( + [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], + completed=set(), + subagents_available=False, + dispatch=lambda task: mutations.append(task.task_id), + wait=lambda _handle: None, + ) + + assert mutations == [] + + +@pytest.mark.parametrize("legacy_value", [True, False, None]) +def test_sg03_legacy_preference_has_no_behavioral_effect(legacy_value: bool | None) -> None: + legacy_migration_input = {} if legacy_value is None else {"prefer_subagent": legacy_value} + events: list[str] = [] + dispatch_ready_wave( + [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], + completed=set(), + subagents_available=True, + dispatch=lambda task: events.append(f"dispatch:{task.task_id}") or provenance(), + wait=lambda _handle: events.append("wait"), + ) + assert legacy_migration_input.get("prefer_subagent") is legacy_value + assert events == ["dispatch:task-001", "wait"] + + +def test_sg04_controller_task_mutation_is_rejected_even_when_validation_passes() -> None: + with pytest.raises(OwnershipBlocker, match="review-blocked") as error: + validate_task_acceptance_ownership( + delegation_evidence=provenance(), + mutation_events=[{"actor_kind": "controller", "paths": ["src/a.py"]}], + write_scope=["src/a.py"], + validations_passed=True, + ) + assert error.value.code == "review-blocked" + + +def test_sg05_independent_disjoint_tasks_dispatch_before_wait() -> None: + events: list[str] = [] + tasks = [ + TaskCandidate("task-a", (), ("src/a.py",), "workspace-a"), + TaskCandidate("task-b", (), ("src/b.py",), "workspace-b"), + ] + + result = dispatch_ready_wave( + tasks, + completed=set(), + subagents_available=True, + dispatch=lambda task: events.append(f"dispatch:{task.task_id}") or task.task_id, + wait=lambda handle: events.append(f"wait:{handle}"), + ) + + assert result.dispatched == ("task-a", "task-b") + assert events == ["dispatch:task-a", "dispatch:task-b", "wait:task-a", "wait:task-b"] + + +def test_sg06_dependent_or_overlapping_tasks_are_serialized() -> None: + events: list[str] = [] + tasks = [ + TaskCandidate("task-a", (), ("src/shared.py",), "workspace-a"), + TaskCandidate("task-b", (), ("src/shared.py",), "workspace-b"), + TaskCandidate("task-c", ("task-a",), ("src/c.py",), "workspace-c"), + ] + result = dispatch_ready_wave( + tasks, + completed=set(), + subagents_available=True, + dispatch=lambda task: events.append(f"dispatch:{task.task_id}") or task.task_id, + wait=lambda handle: events.append(f"wait:{handle}"), + ) + + assert result.dispatched == ("task-a",) + assert events == ["dispatch:task-a", "wait:task-a"] + + +def test_sg06_same_execution_workspace_is_never_fanned_out() -> None: + tasks = [ + TaskCandidate("task-a", (), ("src/a.py",), "workspace-shared"), + TaskCandidate("task-b", (), ("src/b.py",), "workspace-shared"), + ] + result = dispatch_ready_wave( + tasks, + completed=set(), + subagents_available=True, + dispatch=lambda task: task.task_id, + wait=lambda _handle: None, + ) + assert result.dispatched == ("task-a",) + + +def test_sg07_repair_remains_subagent_owned() -> None: + assert normalize_subagent_provenance(provenance(), operation="repair")["agent_id"] == "agent-task-001" + with pytest.raises(OwnershipBlocker, match="workspace-blocked"): + normalize_subagent_provenance( + {**provenance(), "owner_kind": "controller"}, operation="repair" + ) + + +@pytest.mark.parametrize("mechanism", ["host-native", "execution-flow"]) +def test_sg08_ownership_is_mechanism_neutral(mechanism: str) -> None: + normalized = normalize_subagent_provenance(provenance(mechanism=mechanism)) + assert normalized["mechanism"] == mechanism + assert set(normalized) == {"delegated", "owner_kind", "agent_id", "run_id", "mechanism"} + + +def test_visibility_specific_provenance_is_rejected() -> None: + with pytest.raises(OwnershipBlocker, match="workspace-blocked"): + normalize_subagent_provenance({**provenance(), "visible_reference": "thread-123"}) From 53bb36cec8173f74b011b5c8b8b73cb725eb63f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 02:16:54 +0800 Subject: [PATCH 05/36] fix(orchestration): advance serialized task waves --- scripts/orchestration/task_ownership.py | 6 +++++- tests/test_wor108_subagent_ownership.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/scripts/orchestration/task_ownership.py b/scripts/orchestration/task_ownership.py index 07dd99e..b5a3273 100644 --- a/scripts/orchestration/task_ownership.py +++ b/scripts/orchestration/task_ownership.py @@ -113,7 +113,11 @@ def validate_task_acceptance_ownership( def _ready_wave(tasks: Sequence[TaskCandidate], completed: set[str]) -> list[TaskCandidate]: - ready = [task for task in tasks if set(task.dependencies).issubset(completed)] + ready = [ + task + for task in tasks + if task.task_id not in completed and set(task.dependencies).issubset(completed) + ] wave: list[TaskCandidate] = [] for task in ready: if any(task.execution_workspace == selected.execution_workspace for selected in wave): diff --git a/tests/test_wor108_subagent_ownership.py b/tests/test_wor108_subagent_ownership.py index e858d36..9d920ea 100644 --- a/tests/test_wor108_subagent_ownership.py +++ b/tests/test_wor108_subagent_ownership.py @@ -129,6 +129,30 @@ def test_sg06_same_execution_workspace_is_never_fanned_out() -> None: assert result.dispatched == ("task-a",) +def test_sg06_serialized_tasks_progress_across_successive_waves() -> None: + tasks = [ + TaskCandidate("task-a", (), ("src/shared.py",), "workspace-a"), + TaskCandidate("task-b", (), ("src/shared.py",), "workspace-b"), + ] + first = dispatch_ready_wave( + tasks, + completed=set(), + subagents_available=True, + dispatch=lambda task: task.task_id, + wait=lambda _handle: None, + ) + second = dispatch_ready_wave( + tasks, + completed=set(first.dispatched), + subagents_available=True, + dispatch=lambda task: task.task_id, + wait=lambda _handle: None, + ) + + assert first.dispatched == ("task-a",) + assert second.dispatched == ("task-b",) + + def test_sg07_repair_remains_subagent_owned() -> None: assert normalize_subagent_provenance(provenance(), operation="repair")["agent_id"] == "agent-task-001" with pytest.raises(OwnershipBlocker, match="workspace-blocked"): From 11cd54033719be8bf651d57b0f8034d05559d712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 02:39:24 +0800 Subject: [PATCH 06/36] feat(orchestration): bound compiled context projection --- scripts/orchestration/execution_context.py | 312 ++++++++++++++++++++- scripts/work-bundle/stage_events.py | 48 +++- tests/test_wor108_context_projection.py | 239 ++++++++++++++++ 3 files changed, 586 insertions(+), 13 deletions(-) create mode 100644 tests/test_wor108_context_projection.py diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 76b7a9b..2c0b747 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -58,6 +58,27 @@ "missing": "plan", "unexecuted": "task", } +CONTEXT_EXPANSION_REASONS = { + None, + "failed_validation", + "ambiguity", + "reviewer_request", + "authority_gap", +} + + +class _SemanticReference(str): + """Legacy in-memory semantic view that serializes as its stable ID.""" + + def __new__(cls, semantic: str, reference_id: str): + value = super().__new__(cls, semantic) + value.reference_id = reference_id + return value + + def __reduce__(self): + return self.__class__, (str(self), self.reference_id) + + KNOWLEDGE_PERSISTENCE_INSTRUCTION_RE = re.compile( r"(?:\.work-bundle/knowledge(?:/|\b)|\bks-[a-z0-9-]+\b)", re.IGNORECASE, @@ -723,11 +744,188 @@ def _compile_truth_basis( "purpose": purpose.strip(), "as_is_evidence": _resolve_reference(list_fields["as_is_evidence"], records, source_paths), "decision_authority": compiled_authority, - "expected_delta": _resolve_reference(list_fields["expected_delta"], records, source_paths), + # Source meanings are projected once elsewhere; the Truth Basis carries + # their stable IDs rather than duplicating accepted prose. + "expected_delta": _retain_source_references(list_fields["expected_delta"], records), "conflict_status": conflict_status, } +def _retain_source_references(value: Any, records: dict[str, str]) -> Any: + if isinstance(value, str) and value in records: + return value + if isinstance(value, list): + return [_retain_source_references(item, records) for item in value] + if isinstance(value, dict): + return {key: _retain_source_references(item, records) for key, item in value.items()} + return value + + +def _compile_validation_references(value: Any, records: dict[str, str]) -> Any: + if isinstance(value, str) and value in records: + return _SemanticReference(f"{value}: {records[value]}", value) + if isinstance(value, list): + return [_compile_validation_references(item, records) for item in value] + if isinstance(value, dict): + return {key: _compile_validation_references(item, records) for key, item in value.items()} + return value + + +def semantic_digest(value: Any) -> str: + """Return a stable digest for projected semantic or evidence identity.""" + + payload = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + return hashlib.sha256(payload.encode("utf-8")).hexdigest() + + +def _compile_semantic_authority( + source_ids: list[str], + records: dict[str, str], + truth_basis: dict[str, Any], +) -> dict[str, Any]: + """Index each accepted meaning at its single canonical packet location.""" + + index: dict[str, dict[str, str]] = {} + interface_semantics: dict[str, str] = {} + validation_semantics: dict[str, str] = {} + for source_id in source_ids: + if source_id.startswith("CON-"): + field = "constraints" + elif source_id.startswith(("API-", "IFACE-")): + field = "interface_semantics" + interface_semantics[source_id] = f"{source_id}: {records[source_id]}" + elif source_id.startswith("TEST-"): + field = "validation_semantics" + validation_semantics[source_id] = f"{source_id}: {records[source_id]}" + else: + field = "requirements" + index[source_id] = { + "canonical_field": field, + "digest": semantic_digest(records[source_id]), + } + for authority in _as_list(truth_basis.get("decision_authority")): + alias = str(authority).split(":", 1)[0].strip() + if AUTH_ALIAS_RE.fullmatch(alias): + index[alias] = { + "canonical_field": "truth_basis.decision_authority", + "digest": semantic_digest(authority), + } + return { + "records": index, + "interface_semantics": interface_semantics, + "validation_semantics": validation_semantics, + } + + +def _compile_capability_projection( + executor_profile: dict[str, Any], rules: list[dict[str, Any]], skill_names: list[Any] +) -> dict[str, Any]: + return { + "executor": { + "capability": executor_profile["capability"], + "reason": "task executor_profile allocation", + }, + "rules": [ + {"id": str(item["id"]), "reason": str(item["requirement"])} + for item in rules + ], + "skills": [ + {"id": str(name), "reason": "task methodology allocation"} + for name in skill_names + ], + "traversal": "runtime_only", + } + + +def _encoded_bytes(value: Any) -> int: + return len(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")) + + +def compiled_context_metrics( + task_brief: dict[str, Any], + *, + review_package: str | None = None, + evidence_projection: list[dict[str, Any]] | None = None, + omitted_by_reference_bytes: int = 0, + expansion_reason: str | None = None, +) -> dict[str, Any]: + """Measure a compiled packet without imposing a global size policy.""" + + if expansion_reason not in CONTEXT_EXPANSION_REASONS: + raise SystemExit("compiled context expansion_reason is invalid") + semantic_projection = { + "semantic_authority": task_brief.get("semantic_authority", {}), + "requirements": task_brief.get("requirements", []), + "constraints": task_brief.get("constraints", []), + "decision_authority": (task_brief.get("truth_basis") or {}).get("decision_authority", []), + } + return { + "task_brief_bytes": len(("\n".join(_dump_yaml({"task_brief": task_brief})) + "\n").encode("utf-8")), + "semantic_authority_bytes": _encoded_bytes(semantic_projection), + "allocated_rule_bytes": _encoded_bytes(task_brief.get("allocated_rules", [])), + "allocated_skill_bytes": _encoded_bytes((task_brief.get("methodology") or {}).get("skills", [])), + "capability_projection_bytes": _encoded_bytes(task_brief.get("capability_projection", {})), + "evidence_projection_bytes": _encoded_bytes( + evidence_projection if evidence_projection is not None else task_brief.get("evidence_capability", {}) + ), + "review_package_bytes": len(review_package.encode("utf-8")) if review_package is not None else 0, + "omitted_by_reference_bytes": max(0, int(omitted_by_reference_bytes)), + "expansion_reason": expansion_reason, + } + + +def project_validation_evidence( + items: list[dict[str, Any]], + *, + evidence_capability: dict[str, Any], + observed: list[dict[str, Any]] | None = None, + expansion_reason: str | None = None, +) -> list[dict[str, Any]]: + """Project successes as compact receipts and expand only explicit failures.""" + + if expansion_reason not in CONTEXT_EXPANSION_REASONS: + raise SystemExit("evidence projection expansion_reason is invalid") + observed_by_id = { + str(item.get("id")): item for item in (observed or []) if isinstance(item, dict) and item.get("id") + } + invariant_by_evidence: dict[str, dict[str, Any]] = {} + for invariant in _as_list(evidence_capability.get("invariants")): + if not isinstance(invariant, dict): + continue + for evidence_id in _as_list(invariant.get("evidence_ids")): + invariant_by_evidence.setdefault(str(evidence_id), invariant) + projected: list[dict[str, Any]] = [] + for position, item in enumerate(items, start=1): + evidence_id = str(item.get("id") or f"validation-{position:03d}") + result = str(item.get("result") or "ambiguous") + if result not in {"passed", "skipped"}: + reason = expansion_reason or ("failed_validation" if result == "failed" else "ambiguity") + if reason not in CONTEXT_EXPANSION_REASONS - {None}: + raise SystemExit("evidence projection expansion_reason is invalid") + projected.append({ + "id": evidence_id, + "digest": semantic_digest({"command": item.get("command"), "result": result}), + "result": result, + "expansion_reason": reason, + "details": dict(item), + }) + continue + invariant = invariant_by_evidence.get(evidence_id, {}) + observation = observed_by_id.get(evidence_id, {}) + projected.append({ + "id": evidence_id, + "digest": semantic_digest({"command": item.get("command"), "result": result}), + "result": result, + "boundary": invariant.get("boundary", "component"), + "freshness": invariant.get("freshness", "current_task_batch"), + "invalidation_receipt": observation.get("observation_id") or semantic_digest( + {"evidence_id": evidence_id, "result": result} + ), + "expansion_reason": None, + }) + return projected + + def _compile_evidence_capability( task: dict[str, Any], task_id: str, source_ids: list[str], validation: list[dict[str, Any]] ) -> dict[str, Any] | None: @@ -1778,6 +1976,8 @@ def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, def _yaml_scalar(value: Any) -> str: + if isinstance(value, _SemanticReference): + return value.reference_id if value is True: return "true" if value is False: @@ -1992,7 +2192,12 @@ def _compile_task_brief(args: argparse.Namespace) -> tuple[Path, dict[str, Any]] truth_basis = _compile_truth_basis(task, records, source_paths) validation = [ { - key: value if key in {"id", "invariant_ids"} else _resolve_reference(value, records, source_paths) + key: ( + _compile_validation_references(value, records) + if key == "proves" + else value if key in {"id", "invariant_ids"} + else _resolve_reference(value, records, source_paths) + ) for key, value in item.items() } for item in validation_value @@ -2007,30 +2212,48 @@ def _compile_task_brief(args: argparse.Namespace) -> tuple[Path, dict[str, Any]] review_required = acceptance_review.get("required", False) if not isinstance(review_required, bool): raise SystemExit(f"Task acceptance_review.required must be boolean: {task_path}") - brief = { - "task_brief": { + semantic_authority = _compile_semantic_authority(source_ids, records, truth_basis) + capability_projection = _compile_capability_projection(executor_profile, rules, skill_names) + task_brief = { "task_id": task_id, "plan_id": plan_id, "source_ids": source_ids, "goal": resolved_goal, "truth_basis": truth_basis, + "semantic_authority": semantic_authority, "requirements": source_by_kind["requirements"], "constraints": source_by_kind["constraints"], "interfaces": { - "consumes": _resolve_reference(_as_list(interfaces.get("consumes")), records, source_paths), - "produces": _resolve_reference(_as_list(interfaces.get("produces")), records, source_paths), + "consumes": _retain_source_references(_as_list(interfaces.get("consumes")), records), + "produces": _retain_source_references(_as_list(interfaces.get("produces")), records), }, "files": {"read": read_files, "write": write_files, "forbidden": forbidden_files}, "methodology": {"primary": methodology.get("primary", "direct"), "skills": skill_names}, "allocated_rules": rules, + "capability_projection": capability_projection, "executor_profile": executor_profile, "evidence_applicability": evidence_applicability, "workspace": {"root": str(root)}, + "runtime_context": { + "authority": "compiled", + "histories": "runtime_lazy", + "executor_retrieval": "forbidden", + }, "validation": validation, "evidence_capability": evidence_capability, "handoff_contract": "executor-result-v1", "review_required": review_required, - } + } + omitted = 0 + serialized_task = json.dumps(task, sort_keys=True, ensure_ascii=False) + for identifier in source_ids: + duplicate_references = max(0, serialized_task.count(identifier) - 1) + omitted += duplicate_references * max(0, len(records[identifier].encode("utf-8")) - len(identifier.encode("utf-8"))) + brief = { + "task_brief": task_brief, + "compiled_context_metrics": compiled_context_metrics( + task_brief, omitted_by_reference_bytes=omitted + ), } for identifier in source_ids: if not _contains_resolved_source_record(brief, records[identifier]): @@ -2194,6 +2417,19 @@ def build_review_package(args: argparse.Namespace) -> Path: handoff, _ = _read_structured(handoff_path) validated = validate_executor_result_for_task(handoff, task, observe=True, **_observation_kwargs(args)) knowledge_disposition = validated["knowledge_disposition"] + review_request = handoff.get("acceptance_review") if isinstance(handoff.get("acceptance_review"), dict) else {} + review_mode = str(review_request.get("review_mode") or "initial") + if review_mode not in {"initial", "repair"}: + raise SystemExit("review-blocked: review_mode must be initial or repair") + repair_frontier: dict[str, Any] | None = None + if review_mode == "repair": + try: + from review_runtime import ReviewContractError, _repair_frontier + repair_frontier = dict(_repair_frontier(review_request.get("repair_frontier"))) + except (ReviewContractError, TypeError, ValueError) as error: + raise SystemExit(f"review-blocked: invalid repair frontier: {error}") from error + elif review_request.get("repair_frontier") not in (None, {}): + raise SystemExit("review-blocked: initial review cannot carry repair_frontier") binding = load_task_execution_binding(root, plan_id, task_id) execution_root = Path(str(binding["execution_path"])).resolve() @@ -2202,6 +2438,15 @@ def build_review_package(args: argparse.Namespace) -> Path: head, diff, name_status, out_of_scope = _review_diff( execution_root, base, str(args.head), write_paths ) + if repair_frontier is not None: + base_tree = _git(execution_root, "rev-parse", f"{base}^{{tree}}").strip() + if head.startswith("worktree:"): + raise SystemExit("review-blocked: repair review requires a committed repaired identity") + head_tree = _git(execution_root, "rev-parse", f"{head}^{{tree}}").strip() + if repair_frontier["previous_reviewed_identity"]["source_tree"] != base_tree: + raise SystemExit("review-blocked: repair base does not match previous reviewed identity") + if repair_frontier["repaired_identity"]["source_tree"] != head_tree: + raise SystemExit("review-blocked: repair head does not match repaired identity") if len(diff.encode("utf-8")) > MAX_DIFF_BYTES or diff.count("\n") > MAX_DIFF_LINES: oversized = ", ".join( sorted({path for line in name_status for path in _paths_from_name_status(line)}) @@ -2220,11 +2465,26 @@ def build_review_package(args: argparse.Namespace) -> Path: ) validation = handoff.get("validation") if isinstance(handoff.get("validation"), dict) else {} validation_commands = [item for item in _as_list(validation.get("commands")) if isinstance(item, dict)] + compiled_validation = { + str(item.get("command") or ""): item + for item in _as_list(task.get("validation")) + if isinstance(item, dict) + } + normalized_validation = [] + for position, item in enumerate(validation_commands, start=1): + compiled = compiled_validation.get(str(item.get("command") or ""), {}) + normalized_validation.append({**item, "id": item.get("id") or compiled.get("id") or f"validation-{position:03d}"}) + evidence_projection = project_validation_evidence( + normalized_validation, + evidence_capability=task.get("evidence_capability") if isinstance(task.get("evidence_capability"), dict) else {}, + observed=validated.get("observed_validation"), + expansion_reason=("failed_validation" if any(item.get("result") == "failed" for item in normalized_validation) else None), + ) unresolved = _as_list(handoff.get("unresolved")) evidence = { "changed_files": name_status, "changed_symbols": symbols, - "validation": validation_commands, + "validation": evidence_projection, "unresolved": unresolved, "knowledge_disposition": knowledge_disposition, } @@ -2246,6 +2506,7 @@ def build_review_package(args: argparse.Namespace) -> Path: f"Task: {task_id}", f"Base: {base}", f"Head: {head}", + f"Review mode: {review_mode}", "", "## Required behavior", *_markdown_items(required), @@ -2253,6 +2514,9 @@ def build_review_package(args: argparse.Namespace) -> Path: "## Accepted Truth Basis", *_markdown_items([task.get("truth_basis", {})]), "", + "## Semantic authority", + *_markdown_items([task.get("semantic_authority", {})]), + "", "## Evidence capability", *_markdown_items([task.get("evidence_capability", {})]), "", @@ -2266,7 +2530,7 @@ def build_review_package(args: argparse.Namespace) -> Path: *_markdown_items(symbols), "", "## Validation reported", - *_markdown_items(validation_commands), + *_markdown_items(evidence_projection), "", "## Knowledge disposition", *_markdown_items([knowledge_disposition]), @@ -2282,6 +2546,23 @@ def build_review_package(args: argparse.Namespace) -> Path: diff.rstrip(), "```", ] + if repair_frontier is not None: + lines.extend( + [ + "", + "## Repair frontier", + *_markdown_items( + [{ + "prior_review_id": repair_frontier["prior_review_id"], + "blocking_finding_ids": repair_frontier["blocking_finding_ids"], + "previous_reviewed_identity": repair_frontier["previous_reviewed_identity"], + "repaired_identity": repair_frontier["repaired_identity"], + "affected_boundaries": repair_frontier["affected_boundaries"], + "frozen_evidence_reference": repair_frontier["frozen_evidence_reference"], + }] + ), + ] + ) if out_of_scope: lines.extend( [ @@ -2310,6 +2591,19 @@ def build_review_package(args: argparse.Namespace) -> Path: review_target = target.with_name("review-package.md") review_target.parent.mkdir(parents=True, exist_ok=True) review_target.write_text(package, encoding="utf-8") + metrics = compiled_context_metrics( + task, + review_package=package, + evidence_projection=evidence_projection, + expansion_reason=next( + (item.get("expansion_reason") for item in evidence_projection if item.get("expansion_reason")), + None, + ), + ) + review_target.with_name("review-package-metrics.json").write_text( + json.dumps({"compiled_context_metrics": metrics}, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) return review_target diff --git a/scripts/work-bundle/stage_events.py b/scripts/work-bundle/stage_events.py index fa70359..430a9b6 100644 --- a/scripts/work-bundle/stage_events.py +++ b/scripts/work-bundle/stage_events.py @@ -48,7 +48,23 @@ ) IDENTITY_FIELDS = frozenset({"product_tree", "artifact_digest", "mutation_epoch"}) CLOCK_FIELDS = frozenset({"wall_ms", "active_ms", "billed_ms"}) -EVENT_FIELDS = frozenset( +COMPILED_CONTEXT_METRIC_FIELDS = frozenset( + { + "task_brief_bytes", + "semantic_authority_bytes", + "allocated_rule_bytes", + "allocated_skill_bytes", + "capability_projection_bytes", + "evidence_projection_bytes", + "review_package_bytes", + "omitted_by_reference_bytes", + "expansion_reason", + } +) +CONTEXT_EXPANSION_REASONS = frozenset( + {None, "failed_validation", "ambiguity", "reviewer_request", "authority_gap"} +) +LEGACY_EVENT_FIELDS = frozenset( { "event_id", "timestamp", @@ -66,6 +82,7 @@ "privacy", } ) +EVENT_FIELDS = LEGACY_EVENT_FIELDS | {"compiled_context_metrics"} _ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]*$") _OID_RE = re.compile(r"^[0-9a-f]{40}$") @@ -103,9 +120,13 @@ class StageEventV1: owner: str | None identity: dict[str, str | int | None] privacy: str + compiled_context_metrics: dict[str, int | str | None] | None = None def to_dict(self) -> dict[str, object]: - return asdict(self) + result = asdict(self) + if self.compiled_context_metrics is None: + result.pop("compiled_context_metrics") + return result def _fail(code: str) -> None: @@ -133,7 +154,7 @@ def _parse_timestamp(value: object) -> datetime: def _scan_for_sensitive_content(value: object, *, key: str | None = None) -> None: - if key is not None and _SENSITIVE_KEY_RE.search(key): + if key is not None and key != "compiled_context_metrics" and _SENSITIVE_KEY_RE.search(key): _fail("WB_STAGE_EVENT_PRIVACY_INVALID") if isinstance(value, Mapping): for nested_key, nested_value in value.items(): @@ -157,7 +178,10 @@ def validate_stage_event(payload: Mapping[str, object]) -> StageEventV1: """Validate one closed API-004 event without retaining caller-owned containers.""" _scan_for_sensitive_content(payload) - value = _validate_exact_fields(payload, EVENT_FIELDS, "WB_STAGE_EVENT_FIELDS_INVALID") + if set(payload) == LEGACY_EVENT_FIELDS: + value = payload + else: + value = _validate_exact_fields(payload, EVENT_FIELDS, "WB_STAGE_EVENT_FIELDS_INVALID") for field in ("event_id", "process_id", "attempt_id"): if not _is_id(value[field]): _fail("WB_STAGE_EVENT_ID_INVALID") @@ -206,6 +230,21 @@ def validate_stage_event(payload: Mapping[str, object]) -> StageEventV1: if value["privacy"] != "operational_metadata_only": _fail("WB_STAGE_EVENT_PRIVACY_INVALID") + metrics_value = value.get("compiled_context_metrics") + metrics: dict[str, int | str | None] | None = None + if metrics_value is not None: + checked = _validate_exact_fields( + metrics_value, + COMPILED_CONTEXT_METRIC_FIELDS, + "WB_STAGE_EVENT_CONTEXT_METRICS_INVALID", + ) + for field in COMPILED_CONTEXT_METRIC_FIELDS - {"expansion_reason"}: + if not _is_nonnegative_int(checked[field]): + _fail("WB_STAGE_EVENT_CONTEXT_METRICS_INVALID") + if checked["expansion_reason"] not in CONTEXT_EXPANSION_REASONS: + _fail("WB_STAGE_EVENT_CONTEXT_METRICS_INVALID") + metrics = {field: checked[field] for field in sorted(COMPILED_CONTEXT_METRIC_FIELDS)} + return StageEventV1( event_id=str(value["event_id"]), timestamp=str(value["timestamp"]), @@ -221,6 +260,7 @@ def validate_stage_event(payload: Mapping[str, object]) -> StageEventV1: owner=value["owner"] if isinstance(value["owner"], str) else None, identity={field: identity[field] for field in sorted(IDENTITY_FIELDS)}, privacy="operational_metadata_only", + compiled_context_metrics=metrics, ) diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py new file mode 100644 index 0000000..d2bee86 --- /dev/null +++ b/tests/test_wor108_context_projection.py @@ -0,0 +1,239 @@ +from __future__ import annotations + +import json +import sys +from copy import deepcopy +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[1] +ORCHESTRATION = REPO_ROOT / "scripts" / "orchestration" +WORK_BUNDLE = REPO_ROOT / "scripts" / "work-bundle" +for path in (WORK_BUNDLE, ORCHESTRATION): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) + +import execution_context # noqa: E402 +from stage_events import StageEventError, validate_stage_event # noqa: E402 +from test_orchestration_execution_context import ( # noqa: E402 + _bind_task_execution, + _compiled_brief, + _ensure_source_file, + args, + build_review_package, + build_task_brief, + git, + workspace, +) +from test_stage_events import event # noqa: E402 + + +def _document(root: Path, task: Path) -> dict: + return execution_context._read_structured(build_task_brief(args(root, task)))[0] + + +def test_ctx_01_unrelated_runtime_history_does_not_inflate_unchanged_task_brief( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + first = build_task_brief(args(root, task)).read_bytes() + history = root / ".work-bundle/runtime/history/unrelated.jsonl" + history.parent.mkdir(parents=True) + history.write_text("{\"provenance\":\"x\"}\n" * 50_000, encoding="utf-8") + (history.parent / "accepted-handoffs.json").write_text("[]\n", encoding="utf-8") + + second = build_task_brief(args(root, task)).read_bytes() + + assert second == first + + +def test_ctx_01_repair_package_uses_frontier_without_reacquiring_review_history( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + task.write_text( + task.read_text(encoding="utf-8").replace( + "acceptance_review:\n required: false\n", + "acceptance_review:\n required: true\n", + ), + encoding="utf-8", + ) + scoped = _ensure_source_file(root) + git(root, "add", ".") + git(root, "commit", "-qm", "reviewed") + base = git(root, "rev-parse", "HEAD") + base_tree = git(root, "rev-parse", "HEAD^{tree}") + brief = _compiled_brief(root, task) + _bind_task_execution(root, brief) + scoped.write_text("def compile_task():\n return 'repaired'\n", encoding="utf-8") + git(root, "add", str(scoped.relative_to(root))) + git(root, "commit", "-qm", "repaired") + head = git(root, "rev-parse", "HEAD") + head_tree = git(root, "rev-parse", "HEAD^{tree}") + identity = lambda tree, digest: { + "artifact_id": "task-004", "revision": "rev-1", "sha256": digest, "source_tree": tree + } + handoff = { + "id": "handoff-task-004", + "type": "executor-result", + "related": {"plan": "plan-001", "task": "task-004"}, + "result": {"state": "partial"}, + "task_fit_check": {"task": "task-004", "result": "repaired"}, + "acceptance_review": { + "required": True, + "verdict": "pending", + "review_mode": "repair", + "repair_frontier": { + "prior_review_id": "review-prior", + "blocking_finding_ids": ["RF-FINDING-1"], + "previous_reviewed_identity": identity(base_tree, execution_context.semantic_digest("old")), + "repaired_identity": identity(head_tree, execution_context.semantic_digest("new")), + "affected_boundaries": ["compile_task"], + "frozen_evidence_reference": execution_context.semantic_digest("frozen"), + }, + }, + "changes": {"files": [{"path": "scripts/orchestration/execution_context.py", "action": "modified"}]}, + "repository": [{ + "root": str(root.resolve()), "target_kind": "git-backed", + "preflight_kind": "git-clean-worktree", "baseline": "initial", "status": "clean", + }], + "codegraph": [{"root": str(root.resolve()), "applicable": False, "up_to_date": False, "reason": "no-index"}], + "knowledge_disposition": {"action": "none", "reason": "No stable authority changed.", "affected_authority": []}, + "previous_review_history": "MUST-NOT-BE-PROJECTED", + } + handoff_path = root / ".work-bundle/orchestration/handoff/executor/active/handoff-task-004.yaml" + handoff_path.parent.mkdir(parents=True) + handoff_path.write_text("\n".join(execution_context._dump_yaml(handoff)) + "\n", encoding="utf-8") + + package = build_review_package( + args(root, task, handoff=str(handoff_path), base=base, head=head) + ).read_text(encoding="utf-8") + + assert "Review mode: repair" in package + assert "RF-FINDING-1" in package + assert execution_context.semantic_digest("frozen") in package + assert "MUST-NOT-BE-PROJECTED" not in package + assert f"Base: {base}" in package and f"Head: {head}" in package + + +def test_ctx_02_semantic_authority_is_retained_once_and_referenced_by_id( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + document = _document(root, task) + brief = document["task_brief"] + rendered = build_task_brief(args(root, task)).read_text(encoding="utf-8") + + assert brief["semantic_authority"]["records"]["REQ-003"]["canonical_field"] == "requirements" + assert brief["semantic_authority"]["records"]["API-002"]["canonical_field"] == "interface_semantics" + assert brief["interfaces"] == {"consumes": ["API-002"], "produces": ["API-002"]} + assert brief["validation"][0]["proves"] == "TEST-004" + for meaning in ( + "Retry exactly three times before returning failure.", + "Never write outside the assigned files.", + "`compile_task(task: Path) -> dict[str, object]`", + "Focused pytest exits with status 0.", + ): + assert rendered.count(meaning) == 1 + + +def test_ctx_03_executor_capability_projection_contains_only_allocated_capabilities( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + brief = _document(root, task)["task_brief"] + + assert brief["capability_projection"] == { + "executor": {"capability": "mechanical", "reason": "task executor_profile allocation"}, + "rules": [{"id": "scoped-rule", "reason": "Keep the executor packet bounded."}], + "skills": [{"id": "dev-test-driven-development", "reason": "task methodology allocation"}], + "traversal": "runtime_only", + } + assert "parent-rule" not in json.dumps(brief) + + +def test_ctx_04_success_evidence_projects_compact_receipt_not_history_or_stdout() -> None: + projected = execution_context.project_validation_evidence( + [{"id": "VAL-001", "command": "pytest -q", "result": "passed", "stdout": "large output", "history": [1, 2]}], + evidence_capability={ + "invariants": [{"boundary": "component", "freshness": "current_task_batch", "evidence_ids": ["VAL-001"]}] + }, + observed=[{"id": "VAL-001", "observation_id": "observation-001", "result": "passed"}], + ) + + assert projected == [{ + "id": "VAL-001", + "digest": execution_context.semantic_digest({"command": "pytest -q", "result": "passed"}), + "result": "passed", + "boundary": "component", + "freshness": "current_task_batch", + "invalidation_receipt": "observation-001", + "expansion_reason": None, + }] + assert "stdout" not in json.dumps(projected) + assert "history" not in json.dumps(projected) + + +def test_ctx_05_failed_evidence_expands_only_with_allowed_reason() -> None: + item = {"id": "VAL-001", "command": "pytest -q", "result": "failed", "stderr": "assertion failed"} + projected = execution_context.project_validation_evidence( + [item], evidence_capability={"invariants": []}, expansion_reason="failed_validation" + ) + + assert projected[0]["expansion_reason"] == "failed_validation" + assert projected[0]["details"] == item + with pytest.raises(SystemExit, match="expansion_reason"): + execution_context.project_validation_evidence( + [item], evidence_capability={"invariants": []}, expansion_reason="whole_history" + ) + + +def test_ctx_06_no_retrieval_escape_hatch_and_context_metrics_use_existing_telemetry( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + document = _document(root, task) + brief = document["task_brief"] + metrics = document["compiled_context_metrics"] + + assert brief["runtime_context"] == { + "authority": "compiled", + "histories": "runtime_lazy", + "executor_retrieval": "forbidden", + } + assert all(isinstance(value, int) and value >= 0 for key, value in metrics.items() if key != "expansion_reason") + assert metrics["expansion_reason"] is None + assert "hard_limit" not in json.dumps(document) + + payload = event() + payload["compiled_context_metrics"] = metrics + assert validate_stage_event(payload).compiled_context_metrics == metrics + invalid = deepcopy(payload) + invalid["compiled_context_metrics"]["expansion_reason"] = "whole_history" + with pytest.raises(StageEventError, match="WB_STAGE_EVENT_CONTEXT_METRICS_INVALID"): + validate_stage_event(invalid) + + +def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + scoped = _ensure_source_file(root) + git(root, "add", ".") + git(root, "commit", "-qm", "baseline") + brief = _compiled_brief(root, task) + original = _bind_task_execution(root, brief) + scoped.write_text("def compile_task():\n return 'repair'\n", encoding="utf-8") + git(root, "add", str(scoped.relative_to(root))) + git(root, "commit", "-qm", "repair") + + build_task_brief(args(root, task)) + retained = execution_context.capture_task_baseline_once( + execution_context.load_task_execution_binding(root, "plan-001", "task-004") + ) + + assert retained["ownership"]["binding_id"] == original["ownership"]["binding_id"] + assert retained["baseline"] == original["baseline"] + assert retained["baseline"]["head"] != git(root, "rev-parse", "HEAD") From 1cbe41cd7b77cd6de27eec4933cd8cba16f585d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 03:04:11 +0800 Subject: [PATCH 07/36] test(orchestration): add WOR-108 closure fixtures --- evals/wor108/contracts-v1.schema.json | 38 +++++ evals/wor108/fixtures.json | 195 ++++++++++++++++++++++++++ evals/wor108/migration-impact.json | 68 +++++++++ evals/wor108/verify.py | 153 ++++++++++++++++++++ tests/test_wor108_closure.py | 47 +++++++ 5 files changed, 501 insertions(+) create mode 100644 evals/wor108/contracts-v1.schema.json create mode 100644 evals/wor108/fixtures.json create mode 100644 evals/wor108/migration-impact.json create mode 100644 evals/wor108/verify.py create mode 100644 tests/test_wor108_closure.py diff --git a/evals/wor108/contracts-v1.schema.json b/evals/wor108/contracts-v1.schema.json new file mode 100644 index 0000000..fc8a978 --- /dev/null +++ b/evals/wor108/contracts-v1.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:work-bundle:wor108:closure-contracts:v1", + "title": "WOR-108 legacy closure fixture registry", + "type": "object", + "additionalProperties": false, + "required": ["contract", "evaluation_id", "fixtures"], + "properties": { + "contract": {"const": "wor108-closure-fixtures-v1"}, + "evaluation_id": {"const": "wor108-legacy-closure-v1"}, + "fixtures": { + "type": "array", + "minItems": 22, + "maxItems": 22, + "items": {"$ref": "#/$defs/fixture"} + } + }, + "$defs": { + "fixture": { + "type": "object", + "additionalProperties": false, + "required": ["fixture_id", "area", "title", "oracle", "pytest_nodes", "expected"], + "properties": { + "fixture_id": {"pattern": "^(RF|SG|CTX)-[0-9]{2}$"}, + "area": {"enum": ["repair_frontier", "subagent_ownership", "context_projection"]}, + "title": {"type": "string", "minLength": 1}, + "oracle": {"const": "pytest"}, + "pytest_nodes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"type": "string", "pattern": "^tests/test_wor108_[a-z_]+\\.py::test_[a-z0-9_]+$"} + }, + "expected": {"const": "passed"} + } + } + } +} diff --git a/evals/wor108/fixtures.json b/evals/wor108/fixtures.json new file mode 100644 index 0000000..22c8b07 --- /dev/null +++ b/evals/wor108/fixtures.json @@ -0,0 +1,195 @@ +{ + "contract": "wor108-closure-fixtures-v1", + "evaluation_id": "wor108-legacy-closure-v1", + "fixtures": [ + { + "fixture_id": "RF-01", + "area": "repair_frontier", + "title": "Initial and repair review modes are native closed contract values", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_01_initial_and_repair_modes_are_native_and_closed"], + "expected": "passed" + }, + { + "fixture_id": "RF-02", + "area": "repair_frontier", + "title": "Repair frontier binds prior blocking findings, frozen evidence, and exact identities", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_02_repair_frontier_binds_prior_findings_evidence_and_exact_identities"], + "expected": "passed" + }, + { + "fixture_id": "RF-03", + "area": "repair_frontier", + "title": "Repair reuses frozen evidence by reference without copying whole review history", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_03_repair_reuses_frozen_evidence_without_copying_history"], + "expected": "passed" + }, + { + "fixture_id": "RF-04", + "area": "repair_frontier", + "title": "Material redesign or authority boundary change forces a fresh initial review", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_04_material_change_requires_fresh_initial_review"], + "expected": "passed" + }, + { + "fixture_id": "RF-05", + "area": "repair_frontier", + "title": "Fresh initial review requires a fresh capable independent reviewer identity", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_05_reset_rejects_reused_repair_reviewer_identity"], + "expected": "passed" + }, + { + "fixture_id": "RF-06", + "area": "repair_frontier", + "title": "Repair rejects stale or relabelled repaired identities", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_06_repair_rejects_stale_or_relabelled_identity"], + "expected": "passed" + }, + { + "fixture_id": "RF-07", + "area": "repair_frontier", + "title": "Repair catches affected-boundary regressions and retains the original execution binding baseline", + "oracle": "pytest", + "pytest_nodes": [ + "tests/test_wor108_review_frontier.py::test_rf_07_repair_still_detects_regression_in_affected_boundary", + "tests/test_wor108_context_projection.py::test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline" + ], + "expected": "passed" + }, + { + "fixture_id": "RF-08", + "area": "repair_frontier", + "title": "Repair packages stay bounded for task and stage targets", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_08_repair_packet_is_bounded_for_task_and_stage_targets"], + "expected": "passed" + }, + { + "fixture_id": "SG-01", + "area": "subagent_ownership", + "title": "Execute-plan selection implicitly requires subagent task ownership", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg01_execute_plan_requires_implicit_subagent_ownership"], + "expected": "passed" + }, + { + "fixture_id": "SG-02", + "area": "subagent_ownership", + "title": "No-subagent execution fails closed before task mutation", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg02_no_subagent_fails_closed_before_mutation"], + "expected": "passed" + }, + { + "fixture_id": "SG-03", + "area": "subagent_ownership", + "title": "Legacy prefer_subagent metadata has no behavioral effect", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg03_legacy_preference_has_no_behavioral_effect"], + "expected": "passed" + }, + { + "fixture_id": "SG-04", + "area": "subagent_ownership", + "title": "Controller mutation of task write scope is rejected despite green validation", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg04_controller_task_mutation_is_rejected_even_when_validation_passes"], + "expected": "passed" + }, + { + "fixture_id": "SG-05", + "area": "subagent_ownership", + "title": "Independent disjoint tasks fan out before any wait", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg05_independent_disjoint_tasks_dispatch_before_wait"], + "expected": "passed" + }, + { + "fixture_id": "SG-06", + "area": "subagent_ownership", + "title": "Dependent, overlapping, and same-workspace tasks remain serialized and make progress", + "oracle": "pytest", + "pytest_nodes": [ + "tests/test_wor108_subagent_ownership.py::test_sg06_dependent_or_overlapping_tasks_are_serialized", + "tests/test_wor108_subagent_ownership.py::test_sg06_same_execution_workspace_is_never_fanned_out", + "tests/test_wor108_subagent_ownership.py::test_sg06_serialized_tasks_progress_across_successive_waves" + ], + "expected": "passed" + }, + { + "fixture_id": "SG-07", + "area": "subagent_ownership", + "title": "Repair mutation remains subagent-owned", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg07_repair_remains_subagent_owned"], + "expected": "passed" + }, + { + "fixture_id": "SG-08", + "area": "subagent_ownership", + "title": "Ownership provenance is provider-neutral and rejects visibility-specific fields", + "oracle": "pytest", + "pytest_nodes": [ + "tests/test_wor108_subagent_ownership.py::test_sg08_ownership_is_mechanism_neutral", + "tests/test_wor108_subagent_ownership.py::test_visibility_specific_provenance_is_rejected" + ], + "expected": "passed" + }, + { + "fixture_id": "CTX-01", + "area": "context_projection", + "title": "Unrelated provenance growth does not inflate briefs or repair packages", + "oracle": "pytest", + "pytest_nodes": [ + "tests/test_wor108_context_projection.py::test_ctx_01_unrelated_runtime_history_does_not_inflate_unchanged_task_brief", + "tests/test_wor108_context_projection.py::test_ctx_01_repair_package_uses_frontier_without_reacquiring_review_history" + ], + "expected": "passed" + }, + { + "fixture_id": "CTX-02", + "area": "context_projection", + "title": "Semantic authority is retained once and referenced by stable ID elsewhere", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_context_projection.py::test_ctx_02_semantic_authority_is_retained_once_and_referenced_by_id"], + "expected": "passed" + }, + { + "fixture_id": "CTX-03", + "area": "context_projection", + "title": "Executor capability projection contains only allocated capabilities and reasons", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_context_projection.py::test_ctx_03_executor_capability_projection_contains_only_allocated_capabilities"], + "expected": "passed" + }, + { + "fixture_id": "CTX-04", + "area": "context_projection", + "title": "Successful evidence projects compact digest and freshness receipts without history or stdout", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_context_projection.py::test_ctx_04_success_evidence_projects_compact_receipt_not_history_or_stdout"], + "expected": "passed" + }, + { + "fixture_id": "CTX-05", + "area": "context_projection", + "title": "Failed evidence expands only for an allowed explicit reason", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_context_projection.py::test_ctx_05_failed_evidence_expands_only_with_allowed_reason"], + "expected": "passed" + }, + { + "fixture_id": "CTX-06", + "area": "context_projection", + "title": "Compiled context has telemetry, lazy histories, no retrieval escape hatch, and no arbitrary hard limit", + "oracle": "pytest", + "pytest_nodes": ["tests/test_wor108_context_projection.py::test_ctx_06_no_retrieval_escape_hatch_and_context_metrics_use_existing_telemetry"], + "expected": "passed" + } + ] +} diff --git a/evals/wor108/migration-impact.json b/evals/wor108/migration-impact.json new file mode 100644 index 0000000..136a615 --- /dev/null +++ b/evals/wor108/migration-impact.json @@ -0,0 +1,68 @@ +{ + "contract": "wor108-migration-impact-v1", + "issue": "WOR-108", + "evaluation_id": "wor108-legacy-closure-v1", + "accepted_legacy_baseline": { + "commit": "9dce5df221485174d6179f713e8b179bbc20567a", + "tree": "5a1f38355eae8068bab528923e807ce54e6f6fe5" + }, + "changed_surfaces": { + "public_contracts": [ + "references/assets/orchestration/contract/handoff-executor-result-v1.md", + "references/assets/orchestration/contract/stage-review-v1.schema.json", + "references/assets/orchestration/contract/task-v1.md", + "references/assets/orchestration/workflow.md" + ], + "instructions": [ + "references/assets/template/AGENTS.md", + "references/assets/template/bootstrap.yaml", + "references/assets/template/project.yaml", + "rules/orchestration/orch-handoff-required.md", + "rules/work-bundle/wb-project-context-preflight.md", + "skills/orch-create-handoff/SKILL.md", + "skills/orch-doctor/SKILL.md", + "skills/orch-execute-plan/SKILL.md", + "skills/wb-initialize-project/SKILL.md" + ], + "operations": [ + "scripts/orchestration/execution_context.py", + "scripts/orchestration/review_runtime.py", + "scripts/orchestration/task_ownership.py", + "scripts/work-bundle/control_plane.py", + "scripts/work-bundle/core.py", + "scripts/work-bundle/dispatcher.py", + "scripts/work-bundle/migration.py", + "scripts/work-bundle/project.py", + "scripts/work-bundle/reviewer_workspace.py", + "scripts/work-bundle/stage_events.py" + ], + "fixtures_and_evaluations": [ + "evals/wor108/contracts-v1.schema.json", + "evals/wor108/fixtures.json", + "evals/wor108/migration-impact.json", + "evals/wor108/verify.py", + "references/evals/orchestration/evals.json", + "tests/test_control_plane_v4.py", + "tests/test_multi_repository_member.py", + "tests/test_orchestration_execution_context.py", + "tests/test_orchestration_skill_rule_boundary.py", + "tests/test_orchestration_workflow_contracts.py", + "tests/test_project_initialization.py", + "tests/test_registry_layout_migration.py", + "tests/test_wor108_closure.py", + "tests/test_wor108_context_projection.py", + "tests/test_wor108_review_frontier.py", + "tests/test_wor108_subagent_ownership.py" + ] + }, + "evaluation_identities": [ + "RF-01", "RF-02", "RF-03", "RF-04", "RF-05", "RF-06", "RF-07", "RF-08", + "SG-01", "SG-02", "SG-03", "SG-04", "SG-05", "SG-06", "SG-07", "SG-08", + "CTX-01", "CTX-02", "CTX-03", "CTX-04", "CTX-05", "CTX-06" + ], + "handoff_constraints": { + "final_git_identity": "post_acceptance_handoff_only", + "advance_wor107": false, + "touch_work_bundle_mcp": false + } +} diff --git a/evals/wor108/verify.py b/evals/wor108/verify.py new file mode 100644 index 0000000..e7b2c6b --- /dev/null +++ b/evals/wor108/verify.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Verify the deterministic WOR-108 closure and migration-impact package.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re +from typing import Any + + +EVAL_ROOT = Path(__file__).resolve().parent +REPO_ROOT = EVAL_ROOT.parents[1] +EXPECTED_IDS = tuple( + [f"RF-{number:02d}" for number in range(1, 9)] + + [f"SG-{number:02d}" for number in range(1, 9)] + + [f"CTX-{number:02d}" for number in range(1, 7)] +) +EXPECTED_AREAS = { + "RF": "repair_frontier", + "SG": "subagent_ownership", + "CTX": "context_projection", +} +FIXTURE_KEYS = {"fixture_id", "area", "title", "oracle", "pytest_nodes", "expected"} +NODE = re.compile(r"^(tests/test_wor108_[a-z_]+\.py)::(test_[a-z0-9_]+)$") +MIGRATION_KEYS = { + "contract", "issue", "evaluation_id", "accepted_legacy_baseline", + "changed_surfaces", "evaluation_identities", "handoff_constraints", +} +SURFACE_KEYS = {"public_contracts", "instructions", "operations", "fixtures_and_evaluations"} + + +class VerificationError(RuntimeError): + pass + + +def _load(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise VerificationError(f"object required: {path}") + return value + + +def _verify_schema(path: Path) -> None: + schema = _load(path) + if schema.get("$id") != "urn:work-bundle:wor108:closure-contracts:v1": + raise VerificationError("closure schema identity mismatch") + if schema.get("additionalProperties") is not False: + raise VerificationError("closure schema must be closed") + fixture = schema.get("$defs", {}).get("fixture", {}) + if fixture.get("additionalProperties") is not False or set(fixture.get("required", [])) != FIXTURE_KEYS: + raise VerificationError("fixture schema is not a closed required shape") + + +def _verify_fixtures(path: Path) -> tuple[str, ...]: + package = _load(path) + if set(package) != {"contract", "evaluation_id", "fixtures"}: + raise VerificationError("fixture package closed shape mismatch") + if package["contract"] != "wor108-closure-fixtures-v1" or package["evaluation_id"] != "wor108-legacy-closure-v1": + raise VerificationError("fixture package identity mismatch") + fixtures = package["fixtures"] + if not isinstance(fixtures, list): + raise VerificationError("fixtures must be an array") + fixture_ids = tuple(item.get("fixture_id") for item in fixtures if isinstance(item, dict)) + if fixture_ids != EXPECTED_IDS: + raise VerificationError("fixture IDs must be exactly RF-01..08, SG-01..08, CTX-01..06 in order") + seen_nodes: set[str] = set() + for item in fixtures: + if set(item) != FIXTURE_KEYS: + raise VerificationError(f"fixture closed shape mismatch: {item.get('fixture_id')}") + fixture_id = item["fixture_id"] + if item["area"] != EXPECTED_AREAS[fixture_id.split("-", 1)[0]]: + raise VerificationError(f"fixture area mismatch: {fixture_id}") + if item["oracle"] != "pytest" or item["expected"] != "passed" or not item["title"]: + raise VerificationError(f"fixture oracle mismatch: {fixture_id}") + nodes = item["pytest_nodes"] + if not isinstance(nodes, list) or not nodes or len(nodes) != len(set(nodes)): + raise VerificationError(f"fixture pytest node set mismatch: {fixture_id}") + for node in nodes: + match = NODE.fullmatch(node) + if match is None: + raise VerificationError(f"invalid pytest node: {node}") + test_path = REPO_ROOT / match.group(1) + if not test_path.is_file() or f"def {match.group(2)}(" not in test_path.read_text(encoding="utf-8"): + raise VerificationError(f"pytest oracle does not resolve: {node}") + if node in seen_nodes: + raise VerificationError(f"pytest oracle assigned to multiple identities: {node}") + seen_nodes.add(node) + return fixture_ids + + +def _verify_migration(path: Path, fixture_ids: tuple[str, ...]) -> int: + manifest = _load(path) + if set(manifest) != MIGRATION_KEYS: + raise VerificationError("migration-impact closed shape mismatch") + if (manifest["contract"], manifest["issue"], manifest["evaluation_id"]) != ( + "wor108-migration-impact-v1", "WOR-108", "wor108-legacy-closure-v1" + ): + raise VerificationError("migration-impact identity mismatch") + if manifest["accepted_legacy_baseline"] != { + "commit": "9dce5df221485174d6179f713e8b179bbc20567a", + "tree": "5a1f38355eae8068bab528923e807ce54e6f6fe5", + }: + raise VerificationError("accepted WOR-105 baseline mismatch") + surfaces = manifest["changed_surfaces"] + if not isinstance(surfaces, dict) or set(surfaces) != SURFACE_KEYS: + raise VerificationError("changed surface classes mismatch") + flattened = [surface for group in surfaces.values() for surface in group] + if len(flattened) != len(set(flattened)) or any(not (REPO_ROOT / surface).is_file() for surface in flattened): + raise VerificationError("changed surfaces must be unique existing files") + if tuple(manifest["evaluation_identities"]) != fixture_ids: + raise VerificationError("migration evaluation identities mismatch") + if manifest["handoff_constraints"] != { + "final_git_identity": "post_acceptance_handoff_only", + "advance_wor107": False, + "touch_work_bundle_mcp": False, + }: + raise VerificationError("migration handoff constraints mismatch") + forbidden_identity_fields = {"accepted_head", "accepted_tree", "final_commit", "final_tree"} + if forbidden_identity_fields.intersection(manifest): + raise VerificationError("self-referential final Git identity is forbidden") + return len(flattened) + + +def verify( + fixtures_path: Path = EVAL_ROOT / "fixtures.json", + migration_path: Path = EVAL_ROOT / "migration-impact.json", + schema_path: Path = EVAL_ROOT / "contracts-v1.schema.json", +) -> dict[str, Any]: + _verify_schema(schema_path) + fixture_ids = _verify_fixtures(fixtures_path) + surfaces = _verify_migration(migration_path, fixture_ids) + return { + "evaluation_id": "wor108-legacy-closure-v1", + "fixtures": len(fixture_ids), + "changed_surfaces": surfaces, + "verdict": "accepted", + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--fixtures", type=Path, default=EVAL_ROOT / "fixtures.json") + parser.add_argument("--migration-impact", type=Path, default=EVAL_ROOT / "migration-impact.json") + parser.add_argument("--schema", type=Path, default=EVAL_ROOT / "contracts-v1.schema.json") + args = parser.parse_args() + print(json.dumps(verify(args.fixtures, args.migration_impact, args.schema), sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_wor108_closure.py b/tests/test_wor108_closure.py new file mode 100644 index 0000000..82b4a67 --- /dev/null +++ b/tests/test_wor108_closure.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from copy import deepcopy +import importlib.util +import json +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[1] +EVAL_ROOT = REPO_ROOT / "evals" / "wor108" +SPEC = importlib.util.spec_from_file_location("wor108_verify", EVAL_ROOT / "verify.py") +assert SPEC is not None and SPEC.loader is not None +wor108_verify = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(wor108_verify) + + +def test_wor108_closure_registry_has_exact_public_fixture_identities() -> None: + result = wor108_verify.verify() + assert result == { + "evaluation_id": "wor108-legacy-closure-v1", + "fixtures": 22, + "changed_surfaces": 39, + "verdict": "accepted", + } + + +def test_wor108_fixture_registry_rejects_missing_identity(tmp_path: Path) -> None: + fixtures = json.loads((EVAL_ROOT / "fixtures.json").read_text(encoding="utf-8")) + fixtures["fixtures"] = fixtures["fixtures"][:-1] + tampered = tmp_path / "fixtures.json" + tampered.write_text(json.dumps(fixtures), encoding="utf-8") + + with pytest.raises(wor108_verify.VerificationError, match="exactly RF-01..08"): + wor108_verify.verify(fixtures_path=tampered) + + +def test_wor108_migration_impact_rejects_final_git_identity(tmp_path: Path) -> None: + manifest = json.loads((EVAL_ROOT / "migration-impact.json").read_text(encoding="utf-8")) + tampered_manifest = deepcopy(manifest) + tampered_manifest["final_tree"] = "0" * 40 + tampered = tmp_path / "migration-impact.json" + tampered.write_text(json.dumps(tampered_manifest), encoding="utf-8") + + with pytest.raises(wor108_verify.VerificationError, match="closed shape"): + wor108_verify.verify(migration_path=tampered) From 39eab9c3d95db6ffdbdb09a3b3de7f8c773c89c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 03:17:22 +0800 Subject: [PATCH 08/36] fix(evals): type WOR-108 fixture identities --- evals/wor108/contracts-v1.schema.json | 2 +- evals/wor108/verify.py | 8 +++++++- tests/test_wor108_closure.py | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/evals/wor108/contracts-v1.schema.json b/evals/wor108/contracts-v1.schema.json index fc8a978..7c9ed80 100644 --- a/evals/wor108/contracts-v1.schema.json +++ b/evals/wor108/contracts-v1.schema.json @@ -21,7 +21,7 @@ "additionalProperties": false, "required": ["fixture_id", "area", "title", "oracle", "pytest_nodes", "expected"], "properties": { - "fixture_id": {"pattern": "^(RF|SG|CTX)-[0-9]{2}$"}, + "fixture_id": {"type": "string", "pattern": "^(RF|SG|CTX)-[0-9]{2}$"}, "area": {"enum": ["repair_frontier", "subagent_ownership", "context_projection"]}, "title": {"type": "string", "minLength": 1}, "oracle": {"const": "pytest"}, diff --git a/evals/wor108/verify.py b/evals/wor108/verify.py index e7b2c6b..c3e7066 100644 --- a/evals/wor108/verify.py +++ b/evals/wor108/verify.py @@ -49,7 +49,13 @@ def _verify_schema(path: Path) -> None: if schema.get("additionalProperties") is not False: raise VerificationError("closure schema must be closed") fixture = schema.get("$defs", {}).get("fixture", {}) - if fixture.get("additionalProperties") is not False or set(fixture.get("required", [])) != FIXTURE_KEYS: + fixture_id = fixture.get("properties", {}).get("fixture_id", {}) + if ( + fixture.get("type") != "object" + or fixture.get("additionalProperties") is not False + or set(fixture.get("required", [])) != FIXTURE_KEYS + or fixture_id.get("type") != "string" + ): raise VerificationError("fixture schema is not a closed required shape") diff --git a/tests/test_wor108_closure.py b/tests/test_wor108_closure.py index 82b4a67..30e67e9 100644 --- a/tests/test_wor108_closure.py +++ b/tests/test_wor108_closure.py @@ -36,6 +36,32 @@ def test_wor108_fixture_registry_rejects_missing_identity(tmp_path: Path) -> Non wor108_verify.verify(fixtures_path=tampered) +def test_wor108_fixture_schema_rejects_non_object_entries_and_numeric_ids( + tmp_path: Path, +) -> None: + schema = json.loads( + (EVAL_ROOT / "contracts-v1.schema.json").read_text(encoding="utf-8") + ) + fixture_schema = schema["$defs"]["fixture"] + assert fixture_schema["type"] == "object" + assert fixture_schema["properties"]["fixture_id"]["type"] == "string" + + fixtures = json.loads((EVAL_ROOT / "fixtures.json").read_text(encoding="utf-8")) + malformed_entry = deepcopy(fixtures) + malformed_entry["fixtures"][0] = 7 + malformed_entry_path = tmp_path / "malformed-entry.json" + malformed_entry_path.write_text(json.dumps(malformed_entry), encoding="utf-8") + with pytest.raises(wor108_verify.VerificationError, match="exactly RF-01..08"): + wor108_verify.verify(fixtures_path=malformed_entry_path) + + numeric_identity = deepcopy(fixtures) + numeric_identity["fixtures"][0]["fixture_id"] = 1 + numeric_identity_path = tmp_path / "numeric-identity.json" + numeric_identity_path.write_text(json.dumps(numeric_identity), encoding="utf-8") + with pytest.raises(wor108_verify.VerificationError, match="exactly RF-01..08"): + wor108_verify.verify(fixtures_path=numeric_identity_path) + + def test_wor108_migration_impact_rejects_final_git_identity(tmp_path: Path) -> None: manifest = json.loads((EVAL_ROOT / "migration-impact.json").read_text(encoding="utf-8")) tampered_manifest = deepcopy(manifest) From 02ebc7f6ff0571768a3b563932cc04bf271cc91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 03:47:20 +0800 Subject: [PATCH 09/36] test(orchestration): prove bounded repair frontier --- tests/test_wor108_review_frontier.py | 120 +++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/test_wor108_review_frontier.py b/tests/test_wor108_review_frontier.py index bfa5aeb..242585e 100644 --- a/tests/test_wor108_review_frontier.py +++ b/tests/test_wor108_review_frontier.py @@ -2,6 +2,7 @@ import hashlib import json +import subprocess import sys from copy import deepcopy from pathlib import Path @@ -175,3 +176,122 @@ def test_rf_08_repair_packet_is_bounded_for_task_and_stage_targets(tmp_path: Pat task_current = {key: value for key, value in current.items() if key != "stage"} task_current.update(required=True, reviewer_independent=True, verdict="accept", review_target_kind="task", previous_review=task_prior) assert review_runtime.validate_task_acceptance_review(task_current).target_identity == current["target_identity"] + + +def test_rf_01_h1_repairs_recorded_a_without_reacquiring_unrecorded_latent_b(tmp_path: Path) -> None: + target = tmp_path / "repair-a.md" + latent = tmp_path / "latent-b.md" + old_content = "---\nid: artifact-rf01\nversion: 1\n---\nA is broken\n" + new_content = old_content.replace("broken", "repaired") + target.write_text(new_content) + latent.write_text("B is latent and outside H1's recorded surface.\n") + old_identity = review_runtime.artifact_review_identity(target, content=old_content) + new_identity = review_runtime.artifact_review_identity(target) + + initial_h1 = review(target=old_identity, agent="reviewer-h1") + initial_h1.update(review_id="review-h1", verdict="repair", findings=[finding(old_identity, "RF-A")]) + repair_a = review(target=new_identity, mode="repair", agent="reviewer-repair-a") + repair_a["repair_frontier"] = { + "prior_review_id": "review-h1", "blocking_finding_ids": ["RF-A"], + "previous_reviewed_identity": old_identity, "repaired_identity": new_identity, + "affected_boundaries": ["control:repair-a.md"], + "frozen_evidence_reference": review_runtime.review_evidence_identity(initial_h1), + } + assert review_runtime.validate_review_sequence(repair_a, previous_review=initial_h1).verdict == "accepted" + + context = { + "stage": "plan", "target_identity": new_identity, "target_locator": "control:repair-a.md", + "agent_id": "reviewer-repair-a", "capability": "judgment", "execution_id": "exec-rf01", + "evidence_mode": "reproducible_snapshot", "review_mode": "repair", "review_target_kind": "stage", + "repair_frontier": repair_a["repair_frontier"], "review_reset": None, + } + manifest = review_runtime.stage_evidence_manifest( + tmp_path, tmp_path, context, + [{"locator": "control:repair-a.md", "sha256": hashlib.sha256(target.read_bytes()).hexdigest()}], + ) + assert manifest["missing"] == [] + assert [entry["locator"] for entry in manifest["entries"]] == ["control:repair-a.md"] + assert "latent-b.md" not in json.dumps(manifest) + + +@pytest.mark.parametrize( + ("invariant", "finding_class", "obligation", "first_broken"), + [ + ("security", "implementation_defect", "essential_safety", "implementation"), + ("ownership", "implementation_defect", "accepted_requirement", "implementation"), + ("destructive-safety", "implementation_defect", "essential_safety", "implementation"), + ("evidence-integrity", "validation_oracle_defect", "evidence_integrity", "validation_oracle"), + ], +) +def test_rf_03_capable_untouched_invariant_stays_blocking_during_narrow_repair( + invariant: str, finding_class: str, obligation: str, first_broken: str +) -> None: + prior, narrow = repair_pair() + safety = finding(narrow["target_identity"], f"RF-SAFETY-{invariant}") + safety.update( + **{ + "class": finding_class, + "obligation_basis": obligation, + "first_broken_artifact": first_broken, + "recommended_owner": review_runtime.classify_first_broken_owner(finding_class)[1], + "disposition": review_runtime.classify_first_broken_owner(finding_class)[2], + } + ) + safety["evidence"] = [{ + "kind": "test", "locator": f"accepted:{invariant}", + "digest_or_identity": f"RF-03-{invariant}", "observation": "capable evidence still fails", + }] + narrow.update(verdict="repair", findings=[safety]) + assert review_runtime.validate_review_sequence(narrow, previous_review=prior).verdict == "repair" + routed = review_runtime.route_review_verdict(safety) + assert routed["first_broken_artifact"] == first_broken + assert routed["preserve_valid_work_and_evidence"] is True + + +def test_rf_06_final_broad_integrated_review_rediscovers_and_classifies_latent_b(tmp_path: Path) -> None: + for args in (("init", "-q"), ("config", "user.name", "Test"), ("config", "user.email", "test@example.com")): + subprocess.run(["git", "-C", str(tmp_path), *args], check=True) + (tmp_path / ".gitignore").write_text(".work-bundle/\n") + (tmp_path / "direct-a.py").write_text("A = 'repaired'\n") + (tmp_path / "latent-b.py").write_text("B = 'deferred-non-load-bearing'\n") + subprocess.run(["git", "-C", str(tmp_path), "add", "."], check=True) + subprocess.run(["git", "-C", str(tmp_path), "commit", "-qm", "integrated"], check=True) + spec = tmp_path / ".work-bundle/orchestration/spec/active/spec-rf06.md" + spec.parent.mkdir(parents=True) + spec.write_text("---\nid: spec-rf06\nversion: 1\nstatus: verified\n---\nAccepted scope.\n") + plan = tmp_path / ".work-bundle/orchestration/plan/active/plan-rf06.md" + plan.parent.mkdir(parents=True) + plan.write_text("---\nid: plan-rf06\nversion: 1\nstatus: In progress\nsource_spec: [.work-bundle/orchestration/spec/active/spec-rf06.md]\n---\nFinal broad review.\n") + target_identity = review_runtime.stage_target_identity( + tmp_path, "integrated_implementation", plan, source_root=tmp_path + ) + artifacts = [ + {"locator": "control:.work-bundle/orchestration/plan/active/plan-rf06.md", "sha256": hashlib.sha256(plan.read_bytes()).hexdigest()}, + {"locator": "control:.work-bundle/orchestration/spec/active/spec-rf06.md", "sha256": hashlib.sha256(spec.read_bytes()).hexdigest()}, + ] + for entry in review_runtime.source_snapshot_entries(tmp_path): + path = tmp_path / entry["locator"].removeprefix("source:") + artifacts.append({"locator": entry["locator"], "sha256": hashlib.sha256(path.read_bytes()).hexdigest()}) + context = { + "stage": "integrated_implementation", "target_identity": target_identity, + "target_locator": "control:.work-bundle/orchestration/plan/active/plan-rf06.md", + "agent_id": "reviewer-final", "capability": "judgment", "execution_id": "exec-rf06", + "evidence_mode": "reproducible_snapshot", "review_mode": "initial", "review_target_kind": "stage", + "repair_frontier": None, "review_reset": None, + } + manifest = review_runtime.stage_evidence_manifest(tmp_path, tmp_path, context, artifacts) + assert manifest["missing"] == [] + assert "source:latent-b.py" in {entry["locator"] for entry in manifest["source_tree"]} + assert next(entry for entry in manifest["entries"] if entry["locator"] == "source:latent-b.py")["role"] == "source_tree" + + latent_finding = finding(target_identity, "RF-LATENT-B") + latent_finding.update( + **{"class": "advisory_enhancement", "severity": "advisory", "obligation_basis": "none", + "first_broken_artifact": "implementation", "recommended_owner": "backlog_owner", "disposition": "record_advisory"} + ) + latent_finding["evidence"] = [{ + "kind": "source", "locator": "source:latent-b.py", "digest_or_identity": "RF-06-B", + "observation": "Final broad review rediscovered deferred non-load-bearing B.", + }] + assert review_runtime.validate_review_finding(latent_finding).finding_id == "RF-LATENT-B" + assert review_runtime.route_review_verdict(latent_finding)["return_to"] == "backlog_owner" From 473e4b29ca6993a02a5230fe49b7f14c05c8d7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 03:50:25 +0800 Subject: [PATCH 10/36] fix(orchestration): exercise production ownership scheduler --- references/assets/orchestration/workflow.md | 2 +- references/evals/orchestration/evals.json | 10 +- scripts/orchestration/task_ownership.py | 89 +++++++++++--- skills/orch-execute-plan/SKILL.md | 4 +- .../test_orchestration_skill_rule_boundary.py | 2 + tests/test_wor108_subagent_ownership.py | 115 ++++++++++-------- 6 files changed, 143 insertions(+), 79 deletions(-) diff --git a/references/assets/orchestration/workflow.md b/references/assets/orchestration/workflow.md index 442e2ad..82df93d 100644 --- a/references/assets/orchestration/workflow.md +++ b/references/assets/orchestration/workflow.md @@ -193,7 +193,7 @@ scheduler selects executable task Subagent executors own every implementation and repair mutation, task-local verification, and executor-result evidence, including a task-local knowledge disposition of `none`, `update`, `supersede`, or `reclassify`. They never invoke persistence or read knowledge. Reviewers own acceptance judgment for the accepted Truth Basis, requirement fit, correctness, edge cases, test oracle, disposition, unnecessary complexity, allocated obligations, and validation sufficiency. Schedulers own dependencies, barriers, context compilation, neutral subagent binding, validation routing, and evidence shape; they do not perform code-quality review or mutate task write scope. -Selecting `orch-execute-plan` requires a subagent owner for every task without a separate user opt-in. Host-native and Execution-Flow-routed subagents are equivalent; evidence records only the minimum agent/run identity and mechanism. If none is available, execution fails closed before task mutation. Independent disjoint tasks in distinct execution workspaces dispatch before any wait; dependent, overlapping, or same-workspace tasks serialize. +Selecting `orch-execute-plan` requires a subagent owner for every task without a separate user opt-in. The production `TaskOwnershipScheduler` admission entry consumes either a host-native or Execution-Flow adapter; evidence records only the minimum agent/run identity and mechanism. If none is available, execution fails closed before task mutation. Independent disjoint tasks in distinct execution workspaces dispatch before any wait; dependent, overlapping, or same-workspace tasks serialize. Acceptance uses the same scheduler entry to reject controller mutation, and repair dispatch uses `operation: repair` through the same adapter path. On `repair`, return blocking findings with the same brief and current diff to the task-owning subagent. It makes the smallest repair, reruns claim-relevant validation, regenerates the package from the original base, and reviews again. If no subagent is available, fail closed before repair mutation. After two failed low-cost repair rounds, escalate the capability tier; if evidence indicates a plan or specification defect, stop the retry loop and route the typed blocker. diff --git a/references/evals/orchestration/evals.json b/references/evals/orchestration/evals.json index 9fdc0dd..bd1d30f 100644 --- a/references/evals/orchestration/evals.json +++ b/references/evals/orchestration/evals.json @@ -22,7 +22,7 @@ { "id": 4, "prompt": "Execute the next task in an existing plan.", - "expected_output": "Selects execute-plan, requires a subagent owner before task mutation, executes through that subagent when available, and otherwise fails closed with workspace-blocked without changing task scope.", + "expected_output": "Selects execute-plan and invokes the production TaskOwnershipScheduler without requiring a separate user delegation request; it binds a subagent owner before task mutation and otherwise fails closed with workspace-blocked without changing task scope.", "files": [] }, { @@ -34,7 +34,7 @@ { "id": 6, "prompt": "Execute a phase whose next two tasks are independent and have disjoint write scopes, in an environment with sub-agents.", - "expected_output": "Selects execute-plan, binds the independent tasks to separate subagents and isolated execution workspaces, dispatches both before awaiting either, validates executor handoffs against task/phase/plan/spec, updates task statuses, and continues scheduling.", + "expected_output": "Selects execute-plan, passes planner-approved candidates to the production TaskOwnershipScheduler, binds independent tasks to separate subagents and isolated execution workspaces, dispatches both before awaiting either, validates executor handoffs against task/phase/plan/spec, updates task statuses, and continues scheduling.", "files": [] }, { @@ -130,7 +130,7 @@ { "id": 22, "prompt": "Execute a plan task with a delegated sub-agent, then prepare its executor-result handoff after implementation reveals a task-scoped gap.", - "expected_output": "Preserves preflight, accepted baseline, dependencies, write scopes, and the same subagent task owner; the subagent repairs the task-local gap, reruns fresh validation, writes a sparse handoff with neutral ownership evidence, compiles a bounded review package, and requires an accept review before completion.", + "expected_output": "Preserves preflight, accepted baseline, dependencies, and write scopes; routes operation repair through the production TaskOwnershipScheduler to a subagent, reruns fresh validation, writes a sparse handoff with neutral ownership evidence, compiles a bounded review package, and requires an accept review before completion.", "files": [] }, { @@ -178,13 +178,13 @@ { "id": 30, "prompt": "Execute a plan task when no supported subagent execution mechanism is available.", - "expected_output": "Fails closed with workspace-blocked before any task mutation and does not substitute controller or single-agent implementation.", + "expected_output": "The production TaskOwnershipScheduler checks adapter availability and fails closed with workspace-blocked before dispatch or task mutation; it does not substitute controller or single-agent implementation.", "files": [] }, { "id": 31, "prompt": "The orchestration controller directly patches a task-owned file and all task validations pass.", - "expected_output": "Rejects task acceptance with review-blocked because controller mutation violates mandatory subagent ownership; green validation cannot override ownership provenance.", + "expected_output": "TaskOwnershipScheduler.validate_acceptance rejects task acceptance with review-blocked because controller mutation violates mandatory subagent ownership; green validation cannot override ownership provenance.", "files": [] }, { diff --git a/scripts/orchestration/task_ownership.py b/scripts/orchestration/task_ownership.py index b5a3273..563ef01 100644 --- a/scripts/orchestration/task_ownership.py +++ b/scripts/orchestration/task_ownership.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Callable, Iterable, Mapping, Sequence, TypeVar +from typing import Iterable, Mapping, Protocol, Sequence PROVENANCE_FIELDS = frozenset({"delegated", "owner_kind", "agent_id", "run_id", "mechanism"}) @@ -39,6 +39,22 @@ def __post_init__(self) -> None: class DispatchWaveResult: dispatched: tuple[str, ...] results: tuple[object, ...] + ownership: tuple[dict[str, object], ...] + operation: str + + +@dataclass(frozen=True) +class SubagentDispatch: + handle: object + delegation_evidence: Mapping[str, object] + + +class SubagentAdapter(Protocol): + def available(self) -> bool: ... + + def dispatch(self, task: TaskCandidate, *, operation: str) -> SubagentDispatch: ... + + def wait(self, handle: object) -> object: ... def _identifier(value: object, field: str) -> str: @@ -128,22 +144,55 @@ def _ready_wave(tasks: Sequence[TaskCandidate], completed: set[str]) -> list[Tas return wave -Handle = TypeVar("Handle") - - -def dispatch_ready_wave( - tasks: Sequence[TaskCandidate], - *, - completed: set[str], - subagents_available: bool, - dispatch: Callable[[TaskCandidate], Handle], - wait: Callable[[Handle], object], -) -> DispatchWaveResult: - """Dispatch every safe ready task before awaiting any returned handle.""" - - if not subagents_available: - raise OwnershipBlocker("workspace-blocked", "subagent execution is unavailable") - wave = _ready_wave(tasks, completed) - handles = [dispatch(task) for task in wave] - results = tuple(wait(handle) for handle in handles) - return DispatchWaveResult(tuple(task.task_id for task in wave), results) +class TaskOwnershipScheduler: + """Production admission and scheduling entry for ``orch-execute-plan``.""" + + def __init__(self, adapter: SubagentAdapter) -> None: + self._adapter = adapter + + def run_wave( + self, + tasks: Sequence[TaskCandidate], + *, + completed: set[str], + operation: str = "implementation", + ) -> DispatchWaveResult: + if operation not in {"implementation", "repair"}: + raise ValueError("operation must be implementation or repair") + if not self._adapter.available(): + raise OwnershipBlocker("workspace-blocked", "subagent execution is unavailable") + wave = _ready_wave(tasks, completed) + dispatched: list[SubagentDispatch] = [] + ownership: list[dict[str, object]] = [] + for task in wave: + receipt = self._adapter.dispatch(task, operation=operation) + if not isinstance(receipt, SubagentDispatch): + raise OwnershipBlocker("workspace-blocked", "subagent dispatch receipt is invalid") + ownership.append( + normalize_subagent_provenance(receipt.delegation_evidence, operation=operation) + ) + dispatched.append(receipt) + results = tuple(self._adapter.wait(receipt.handle) for receipt in dispatched) + return DispatchWaveResult( + dispatched=tuple(task.task_id for task in wave), + results=results, + ownership=tuple(ownership), + operation=operation, + ) + + def validate_acceptance( + self, + *, + delegation_evidence: Mapping[str, object] | None, + mutation_events: Iterable[Mapping[str, object]], + write_scope: Sequence[str], + validations_passed: bool, + operation: str = "implementation", + ) -> dict[str, object]: + return validate_task_acceptance_ownership( + delegation_evidence=delegation_evidence, + mutation_events=mutation_events, + write_scope=write_scope, + validations_passed=validations_passed, + operation=operation, + ) diff --git a/skills/orch-execute-plan/SKILL.md b/skills/orch-execute-plan/SKILL.md index 85037bc..876efa0 100644 --- a/skills/orch-execute-plan/SKILL.md +++ b/skills/orch-execute-plan/SKILL.md @@ -24,8 +24,8 @@ python3 scripts/orch.py build-task-brief --task Missing source IDs; decision authority other than `none-relevant` or an `AUTH-NNN` alias whose carried constraint was reconciled in the verified specification; `conflict_status: escalate`; inconsistent scope; or unsafe workspace state fails closed with the existing typed blocker. Truth Basis conflict uses `decision-blocked`. The compiled brief includes `AUTH-NNN: `, not the alias alone. 7. Choose the provider-neutral capability from the task profile. Consume planner-proven dependencies, write scopes, common-contract groups, barriers, convergence ownership, and isolation requirements. Dispatch every ready independent task with disjoint write scope to a separate execution workspace before awaiting any result. Serialize dependent, overlapping, or same-workspace mutation. Contract-decoupled participants validate against the common contract, accepted prior handoffs, and task-local files; they reach the named barrier before convergence work. -8. Selecting `orch-execute-plan` makes every implementation and repair task subagent-owned. Before any task mutation, confirm a host-native or Execution-Flow-routed subagent is available and bind the task to its neutral agent/run identity. If no subagent is available, fail closed with `workspace-blocked`; there is no controller or single-agent fallback. Do not substitute `reviewer_independent: false` for a missing task owner. The orchestration thread may schedule, compile briefs, coordinate barriers, validate results, route reviews, and manage lifecycle, but it must not implement or repair task write scope. -9. Always validate neutral `delegation_evidence` and reject acceptance when mutation provenance shows the controller changed task-owned write scope, even if validation is green. Then validate the executor-result with the shared helper. For a mapped capability task, report each validation under its compiled evidence ID and invariant IDs, and add `evidence_closure` using the allocated boundary, freshness, and evidence IDs. The helper observes required process/inspection items in the bound worktree as one Git-state-neutral batch, reuses the compiled identities, and rejects missing, incapable, contradictory, stale, wrong-boundary, failed, or unexecuted evidence before authorizing from post-execution task-caused delta. Executor closure claims are corroboration, not harness proof. Compile `build-review-package` and assign `dev-code-review` only when `acceptance_review.required: true` or compiled `review_required: true`. The scheduler does not perform code-quality review. +8. Selecting `orch-execute-plan` makes every implementation and repair task subagent-owned. Use the production `TaskOwnershipScheduler` entry in `scripts/orchestration/task_ownership.py` with a host-native or Execution-Flow adapter; do not reproduce its admission logic in prompts or detached helpers. Before any task mutation, it confirms adapter availability and binds each dispatched task to validated neutral agent/run provenance. If no subagent is available, it fails closed with `workspace-blocked`; there is no controller or single-agent fallback. Do not substitute `reviewer_independent: false` for a missing task owner. The orchestration thread may schedule, compile briefs, coordinate barriers, validate results, route reviews, and manage lifecycle, but it must not implement or repair task write scope. +9. Use `TaskOwnershipScheduler.validate_acceptance` to validate neutral `delegation_evidence` and reject acceptance when mutation provenance shows the controller changed task-owned write scope, even if validation is green. Then validate the executor-result with the shared helper. For a mapped capability task, report each validation under its compiled evidence ID and invariant IDs, and add `evidence_closure` using the allocated boundary, freshness, and evidence IDs. The helper observes required process/inspection items in the bound worktree as one Git-state-neutral batch, reuses the compiled identities, and rejects missing, incapable, contradictory, stale, wrong-boundary, failed, or unexecuted evidence before authorizing from post-execution task-caused delta. Executor closure claims are corroboration, not harness proof. Compile `build-review-package` and assign `dev-code-review` only when `acceptance_review.required: true` or compiled `review_required: true`. The scheduler does not perform code-quality review. ```bash python3 scripts/orch.py validate-executor-result --task --handoff diff --git a/tests/test_orchestration_skill_rule_boundary.py b/tests/test_orchestration_skill_rule_boundary.py index f6e34b4..bda6d3c 100644 --- a/tests/test_orchestration_skill_rule_boundary.py +++ b/tests/test_orchestration_skill_rule_boundary.py @@ -123,6 +123,8 @@ def test_execute_skill_uses_compiler_independent_review_and_typed_blockers() -> "dev-code-review", "The scheduler does not perform code-quality review", "every implementation and repair task subagent-owned", + "TaskOwnershipScheduler", + "TaskOwnershipScheduler.validate_acceptance", "there is no controller or single-agent fallback", "must not implement or repair task write scope", "After two failed repair rounds", diff --git a/tests/test_wor108_subagent_ownership.py b/tests/test_wor108_subagent_ownership.py index 9d920ea..0e47e4e 100644 --- a/tests/test_wor108_subagent_ownership.py +++ b/tests/test_wor108_subagent_ownership.py @@ -11,10 +11,10 @@ from task_ownership import ( # type: ignore[import-not-found] OwnershipBlocker, + SubagentDispatch, TaskCandidate, - dispatch_ready_wave, + TaskOwnershipScheduler, normalize_subagent_provenance, - validate_task_acceptance_ownership, ) @@ -28,46 +28,62 @@ def provenance(*, mechanism: str = "host-native") -> dict[str, object]: } +class RecordingAdapter: + def __init__(self, *, available: bool = True, mechanism: str = "host-native") -> None: + self.is_available = available + self.mechanism = mechanism + self.events: list[str] = [] + + def available(self) -> bool: + return self.is_available + + def dispatch(self, task: TaskCandidate, *, operation: str) -> SubagentDispatch: + self.events.append(f"dispatch:{task.task_id}:{operation}") + return SubagentDispatch(task.task_id, provenance(mechanism=self.mechanism)) + + def wait(self, handle: object) -> object: + self.events.append(f"wait:{handle}") + return handle + + def test_sg01_execute_plan_requires_implicit_subagent_ownership() -> None: - assert normalize_subagent_provenance(provenance())["owner_kind"] == "subagent" - with pytest.raises(OwnershipBlocker, match="workspace-blocked") as error: - normalize_subagent_provenance(None) - assert error.value.code == "workspace-blocked" + adapter = RecordingAdapter() + result = TaskOwnershipScheduler(adapter).run_wave( + [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set() + ) + assert result.dispatched == ("task-001",) + assert result.ownership[0]["owner_kind"] == "subagent" + assert adapter.events == ["dispatch:task-001:implementation", "wait:task-001"] def test_sg02_no_subagent_fails_closed_before_mutation() -> None: - mutations: list[str] = [] + adapter = RecordingAdapter(available=False) with pytest.raises(OwnershipBlocker, match="workspace-blocked"): - dispatch_ready_wave( + TaskOwnershipScheduler(adapter).run_wave( [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set(), - subagents_available=False, - dispatch=lambda task: mutations.append(task.task_id), - wait=lambda _handle: None, ) - assert mutations == [] + assert adapter.events == [] @pytest.mark.parametrize("legacy_value", [True, False, None]) def test_sg03_legacy_preference_has_no_behavioral_effect(legacy_value: bool | None) -> None: legacy_migration_input = {} if legacy_value is None else {"prefer_subagent": legacy_value} - events: list[str] = [] - dispatch_ready_wave( + adapter = RecordingAdapter() + TaskOwnershipScheduler(adapter).run_wave( [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set(), - subagents_available=True, - dispatch=lambda task: events.append(f"dispatch:{task.task_id}") or provenance(), - wait=lambda _handle: events.append("wait"), ) assert legacy_migration_input.get("prefer_subagent") is legacy_value - assert events == ["dispatch:task-001", "wait"] + assert adapter.events == ["dispatch:task-001:implementation", "wait:task-001"] def test_sg04_controller_task_mutation_is_rejected_even_when_validation_passes() -> None: + scheduler = TaskOwnershipScheduler(RecordingAdapter()) with pytest.raises(OwnershipBlocker, match="review-blocked") as error: - validate_task_acceptance_ownership( + scheduler.validate_acceptance( delegation_evidence=provenance(), mutation_events=[{"actor_kind": "controller", "paths": ["src/a.py"]}], write_scope=["src/a.py"], @@ -77,41 +93,40 @@ def test_sg04_controller_task_mutation_is_rejected_even_when_validation_passes() def test_sg05_independent_disjoint_tasks_dispatch_before_wait() -> None: - events: list[str] = [] + adapter = RecordingAdapter() tasks = [ TaskCandidate("task-a", (), ("src/a.py",), "workspace-a"), TaskCandidate("task-b", (), ("src/b.py",), "workspace-b"), ] - result = dispatch_ready_wave( + result = TaskOwnershipScheduler(adapter).run_wave( tasks, completed=set(), - subagents_available=True, - dispatch=lambda task: events.append(f"dispatch:{task.task_id}") or task.task_id, - wait=lambda handle: events.append(f"wait:{handle}"), ) assert result.dispatched == ("task-a", "task-b") - assert events == ["dispatch:task-a", "dispatch:task-b", "wait:task-a", "wait:task-b"] + assert adapter.events == [ + "dispatch:task-a:implementation", + "dispatch:task-b:implementation", + "wait:task-a", + "wait:task-b", + ] def test_sg06_dependent_or_overlapping_tasks_are_serialized() -> None: - events: list[str] = [] + adapter = RecordingAdapter() tasks = [ TaskCandidate("task-a", (), ("src/shared.py",), "workspace-a"), TaskCandidate("task-b", (), ("src/shared.py",), "workspace-b"), TaskCandidate("task-c", ("task-a",), ("src/c.py",), "workspace-c"), ] - result = dispatch_ready_wave( + result = TaskOwnershipScheduler(adapter).run_wave( tasks, completed=set(), - subagents_available=True, - dispatch=lambda task: events.append(f"dispatch:{task.task_id}") or task.task_id, - wait=lambda handle: events.append(f"wait:{handle}"), ) assert result.dispatched == ("task-a",) - assert events == ["dispatch:task-a", "wait:task-a"] + assert adapter.events == ["dispatch:task-a:implementation", "wait:task-a"] def test_sg06_same_execution_workspace_is_never_fanned_out() -> None: @@ -119,12 +134,9 @@ def test_sg06_same_execution_workspace_is_never_fanned_out() -> None: TaskCandidate("task-a", (), ("src/a.py",), "workspace-shared"), TaskCandidate("task-b", (), ("src/b.py",), "workspace-shared"), ] - result = dispatch_ready_wave( + result = TaskOwnershipScheduler(RecordingAdapter()).run_wave( tasks, completed=set(), - subagents_available=True, - dispatch=lambda task: task.task_id, - wait=lambda _handle: None, ) assert result.dispatched == ("task-a",) @@ -134,19 +146,14 @@ def test_sg06_serialized_tasks_progress_across_successive_waves() -> None: TaskCandidate("task-a", (), ("src/shared.py",), "workspace-a"), TaskCandidate("task-b", (), ("src/shared.py",), "workspace-b"), ] - first = dispatch_ready_wave( + scheduler = TaskOwnershipScheduler(RecordingAdapter()) + first = scheduler.run_wave( tasks, completed=set(), - subagents_available=True, - dispatch=lambda task: task.task_id, - wait=lambda _handle: None, ) - second = dispatch_ready_wave( + second = scheduler.run_wave( tasks, completed=set(first.dispatched), - subagents_available=True, - dispatch=lambda task: task.task_id, - wait=lambda _handle: None, ) assert first.dispatched == ("task-a",) @@ -154,18 +161,24 @@ def test_sg06_serialized_tasks_progress_across_successive_waves() -> None: def test_sg07_repair_remains_subagent_owned() -> None: - assert normalize_subagent_provenance(provenance(), operation="repair")["agent_id"] == "agent-task-001" - with pytest.raises(OwnershipBlocker, match="workspace-blocked"): - normalize_subagent_provenance( - {**provenance(), "owner_kind": "controller"}, operation="repair" - ) + adapter = RecordingAdapter() + result = TaskOwnershipScheduler(adapter).run_wave( + [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], + completed=set(), + operation="repair", + ) + assert result.operation == "repair" + assert result.ownership[0]["owner_kind"] == "subagent" + assert adapter.events == ["dispatch:task-001:repair", "wait:task-001"] @pytest.mark.parametrize("mechanism", ["host-native", "execution-flow"]) def test_sg08_ownership_is_mechanism_neutral(mechanism: str) -> None: - normalized = normalize_subagent_provenance(provenance(mechanism=mechanism)) - assert normalized["mechanism"] == mechanism - assert set(normalized) == {"delegated", "owner_kind", "agent_id", "run_id", "mechanism"} + result = TaskOwnershipScheduler(RecordingAdapter(mechanism=mechanism)).run_wave( + [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set() + ) + assert result.ownership[0]["mechanism"] == mechanism + assert set(result.ownership[0]) == {"delegated", "owner_kind", "agent_id", "run_id", "mechanism"} def test_visibility_specific_provenance_is_rejected() -> None: From a03b96fbe5b2a6160a8da70e688b8fef2be8f08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 04:06:33 +0800 Subject: [PATCH 11/36] test(orchestration): close WOR-108 SG oracle gaps --- scripts/orchestration/task_ownership.py | 90 ++++++++++++- tests/test_wor108_subagent_ownership.py | 170 ++++++++++++++++++++++-- 2 files changed, 242 insertions(+), 18 deletions(-) diff --git a/scripts/orchestration/task_ownership.py b/scripts/orchestration/task_ownership.py index 563ef01..07298c3 100644 --- a/scripts/orchestration/task_ownership.py +++ b/scripts/orchestration/task_ownership.py @@ -25,6 +25,11 @@ class TaskCandidate: dependencies: tuple[str, ...] write_scope: tuple[str, ...] execution_workspace: str + common_contract: str | None = None + barrier: str | None = None + convergence_owner: str | None = None + barrier_participants: tuple[str, ...] = () + binding_id: str | None = None def __post_init__(self) -> None: if not self.task_id.strip(): @@ -35,12 +40,33 @@ def __post_init__(self) -> None: raise ValueError("execution_workspace must be non-empty") +@dataclass(frozen=True) +class RepairContinuity: + """Harness-owned identities a repair must carry without reacquisition.""" + + binding_id: str + baseline_identity: str + evidence_identity: str + previous_review_identity: str + + def __post_init__(self) -> None: + for field in ( + "binding_id", + "baseline_identity", + "evidence_identity", + "previous_review_identity", + ): + if not str(getattr(self, field)).strip(): + raise ValueError(f"{field} must be non-empty") + + @dataclass(frozen=True) class DispatchWaveResult: dispatched: tuple[str, ...] results: tuple[object, ...] ownership: tuple[dict[str, object], ...] operation: str + repair_continuity: tuple[RepairContinuity, ...] = () @dataclass(frozen=True) @@ -128,11 +154,38 @@ def validate_task_acceptance_ownership( return provenance -def _ready_wave(tasks: Sequence[TaskCandidate], completed: set[str]) -> list[TaskCandidate]: +def _validate_topology(task: TaskCandidate) -> None: + participant_fields = (task.common_contract, task.barrier, task.convergence_owner) + if any(participant_fields) and not all(participant_fields) and not task.barrier_participants: + raise OwnershipBlocker( + "workspace-blocked", + f"{task.task_id} has incomplete common-contract/barrier topology", + ) + if task.barrier_participants: + if not task.common_contract or not task.barrier: + raise OwnershipBlocker( + "workspace-blocked", + f"{task.task_id} convergence admission lacks common contract or barrier", + ) + if not set(task.barrier_participants).issubset(task.dependencies): + raise OwnershipBlocker( + "workspace-blocked", + f"{task.task_id} convergence dependencies omit barrier participants", + ) + + +def _ready_wave( + tasks: Sequence[TaskCandidate], + completed: set[str], + accepted_handoffs: set[str], +) -> list[TaskCandidate]: + for task in tasks: + _validate_topology(task) ready = [ task for task in tasks if task.task_id not in completed and set(task.dependencies).issubset(completed) + and set(task.barrier_participants).issubset(accepted_handoffs) ] wave: list[TaskCandidate] = [] for task in ready: @@ -156,21 +209,49 @@ def run_wave( *, completed: set[str], operation: str = "implementation", + accepted_handoffs: set[str] | None = None, + prior_ownership: Mapping[str, Mapping[str, object]] | None = None, + repair_continuity: Mapping[str, RepairContinuity] | None = None, + authorized_replacements: set[str] | None = None, ) -> DispatchWaveResult: if operation not in {"implementation", "repair"}: raise ValueError("operation must be implementation or repair") if not self._adapter.available(): raise OwnershipBlocker("workspace-blocked", "subagent execution is unavailable") - wave = _ready_wave(tasks, completed) + wave = _ready_wave(tasks, completed, accepted_handoffs or set()) + if operation == "repair": + for task in wave: + if (prior_ownership or {}).get(task.task_id) is None: + raise OwnershipBlocker( + "review-blocked", f"{task.task_id} repair lacks prior owner" + ) + if (repair_continuity or {}).get(task.task_id) is None: + raise OwnershipBlocker( + "review-blocked", f"{task.task_id} repair lacks continuity identities" + ) dispatched: list[SubagentDispatch] = [] ownership: list[dict[str, object]] = [] + retained: list[RepairContinuity] = [] for task in wave: receipt = self._adapter.dispatch(task, operation=operation) if not isinstance(receipt, SubagentDispatch): raise OwnershipBlocker("workspace-blocked", "subagent dispatch receipt is invalid") - ownership.append( - normalize_subagent_provenance(receipt.delegation_evidence, operation=operation) + current_owner = normalize_subagent_provenance( + receipt.delegation_evidence, operation=operation ) + if operation == "repair": + previous = (prior_ownership or {}).get(task.task_id) + continuity = (repair_continuity or {}).get(task.task_id) + assert previous is not None and continuity is not None + previous_owner = normalize_subagent_provenance(previous) + replaced = current_owner["agent_id"] != previous_owner["agent_id"] + if replaced and task.task_id not in (authorized_replacements or set()): + raise OwnershipBlocker( + "review-blocked", + f"{task.task_id} repair owner replacement is not authorized", + ) + retained.append(continuity) + ownership.append(current_owner) dispatched.append(receipt) results = tuple(self._adapter.wait(receipt.handle) for receipt in dispatched) return DispatchWaveResult( @@ -178,6 +259,7 @@ def run_wave( results=results, ownership=tuple(ownership), operation=operation, + repair_continuity=tuple(retained), ) def validate_acceptance( diff --git a/tests/test_wor108_subagent_ownership.py b/tests/test_wor108_subagent_ownership.py index 0e47e4e..2d6cfc4 100644 --- a/tests/test_wor108_subagent_ownership.py +++ b/tests/test_wor108_subagent_ownership.py @@ -1,6 +1,9 @@ from __future__ import annotations from pathlib import Path +import json +import os +import subprocess import sys import pytest @@ -11,6 +14,7 @@ from task_ownership import ( # type: ignore[import-not-found] OwnershipBlocker, + RepairContinuity, SubagentDispatch, TaskCandidate, TaskOwnershipScheduler, @@ -46,6 +50,33 @@ def wait(self, handle: object) -> object: return handle +class HeldOpenAdapter(RecordingAdapter): + """Workers remain pending until wait; waiting early exposes lost fan-out.""" + + def __init__(self, expected_dispatches: int) -> None: + super().__init__() + self.expected_dispatches = expected_dispatches + + def wait(self, handle: object) -> object: + dispatches = [event for event in self.events if event.startswith("dispatch:")] + assert len(dispatches) == self.expected_dispatches + self.events.append(f"wait:{handle}") + return {"handoff": f"handoff-{handle}", "accepted": True} + + +def run_wb(config_root: Path, *args: str, cwd: Path) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env["WB_CONFIG_ROOT"] = str(config_root) + return subprocess.run( + [sys.executable, str(REPO_ROOT / "scripts" / "wb.py"), *args], + cwd=cwd, + env=env, + check=False, + capture_output=True, + text=True, + ) + + def test_sg01_execute_plan_requires_implicit_subagent_ownership() -> None: adapter = RecordingAdapter() result = TaskOwnershipScheduler(adapter).run_wave( @@ -69,14 +100,48 @@ def test_sg02_no_subagent_fails_closed_before_mutation() -> None: @pytest.mark.parametrize("legacy_value", [True, False, None]) -def test_sg03_legacy_preference_has_no_behavioral_effect(legacy_value: bool | None) -> None: - legacy_migration_input = {} if legacy_value is None else {"prefer_subagent": legacy_value} +def test_sg03_legacy_preference_has_no_behavioral_effect( + tmp_path: Path, legacy_value: bool | None +) -> None: + config_root = tmp_path / "config" + registry = config_root / "registry" + registry.mkdir(parents=True) + preference = "" if legacy_value is None else f"prefer_subagent: {str(legacy_value).lower()}\n" + (config_root / "bootstrap.yaml").write_text( + "bootstrap_version: v1\n" + f"work_bundle_root: {REPO_ROOT}\n" + 'project_registry: "$work_bundle_config_root/registry/projects.yaml"\n' + 'skill_registry: "$work_bundle_config_root/registry/skill-registry.yaml"\n' + + preference, + encoding="utf-8", + ) + (registry / "projects.yaml").write_text("projects: []\n", encoding="utf-8") + (registry / "skill-registry.yaml").write_text("skills: []\n", encoding="utf-8") + project = tmp_path / "project" + project.mkdir() + initialized = run_wb( + config_root, + "init-project", + str(project), + "--mode", + "single-repository", + cwd=project, + ) + assert initialized.returncode == 0, initialized.stdout + initialized.stderr + metadata = project / ".work-bundle" / "project.yaml" + metadata.write_text(metadata.read_text(encoding="utf-8") + preference, encoding="utf-8") + + shown = run_wb(config_root, "show-project", "--project-root", str(project), cwd=project) + assert shown.returncode == 0, shown.stdout + shown.stderr + assert "prefer_subagent" not in json.loads(shown.stdout) + assert "prefer_subagent" not in (project / "AGENTS.md").read_text(encoding="utf-8") + assert "prefer_subagent" not in json.loads(initialized.stdout) + adapter = RecordingAdapter() TaskOwnershipScheduler(adapter).run_wave( [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set(), ) - assert legacy_migration_input.get("prefer_subagent") is legacy_value assert adapter.events == ["dispatch:task-001:implementation", "wait:task-001"] @@ -92,11 +157,17 @@ def test_sg04_controller_task_mutation_is_rejected_even_when_validation_passes() assert error.value.code == "review-blocked" -def test_sg05_independent_disjoint_tasks_dispatch_before_wait() -> None: - adapter = RecordingAdapter() +def test_sg05_independent_disjoint_tasks_dispatch_before_wait_and_convergence() -> None: + adapter = HeldOpenAdapter(expected_dispatches=2) tasks = [ - TaskCandidate("task-a", (), ("src/a.py",), "workspace-a"), - TaskCandidate("task-b", (), ("src/b.py",), "workspace-b"), + TaskCandidate( + "task-a", (), ("src/a.py",), "workspace-a", + common_contract="CG-001", barrier="BAR-001", convergence_owner="task-c", + ), + TaskCandidate( + "task-b", (), ("src/b.py",), "workspace-b", + common_contract="CG-001", barrier="BAR-001", convergence_owner="task-c", + ), ] result = TaskOwnershipScheduler(adapter).run_wave( @@ -111,6 +182,33 @@ def test_sg05_independent_disjoint_tasks_dispatch_before_wait() -> None: "wait:task-a", "wait:task-b", ] + assert result.results == ( + {"handoff": "handoff-task-a", "accepted": True}, + {"handoff": "handoff-task-b", "accepted": True}, + ) + + convergence = TaskCandidate( + "task-c", ("task-a", "task-b"), ("tests/integration.py",), "workspace-c", + common_contract="CG-001", barrier="BAR-001", barrier_participants=("task-a", "task-b"), + ) + closed = TaskOwnershipScheduler(RecordingAdapter()).run_wave( + [convergence], completed={"task-a", "task-b"}, accepted_handoffs={"task-a"} + ) + assert closed.dispatched == () + released = TaskOwnershipScheduler(RecordingAdapter()).run_wave( + [convergence], + completed={"task-a", "task-b"}, + accepted_handoffs={"task-a", "task-b"}, + ) + assert released.dispatched == ("task-c",) + + +def test_sg05_rejects_incomplete_common_contract_barrier_topology() -> None: + incomplete = TaskCandidate( + "task-a", (), ("src/a.py",), "workspace-a", common_contract="CG-001" + ) + with pytest.raises(OwnershipBlocker, match="workspace-blocked"): + TaskOwnershipScheduler(RecordingAdapter()).run_wave([incomplete], completed=set()) def test_sg06_dependent_or_overlapping_tasks_are_serialized() -> None: @@ -160,25 +258,69 @@ def test_sg06_serialized_tasks_progress_across_successive_waves() -> None: assert second.dispatched == ("task-b",) -def test_sg07_repair_remains_subagent_owned() -> None: +def test_sg07_repair_retains_owner_binding_baseline_evidence_and_frontier() -> None: adapter = RecordingAdapter() + continuity = RepairContinuity( + binding_id="binding-task-001", + baseline_identity="base-tree-001", + evidence_identity="evidence-batch-001", + previous_review_identity="review-head-001", + ) result = TaskOwnershipScheduler(adapter).run_wave( [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set(), operation="repair", + prior_ownership={"task-001": provenance()}, + repair_continuity={"task-001": continuity}, ) assert result.operation == "repair" assert result.ownership[0]["owner_kind"] == "subagent" + assert result.ownership[0]["agent_id"] == provenance()["agent_id"] + assert result.repair_continuity == (continuity,) assert adapter.events == ["dispatch:task-001:repair", "wait:task-001"] -@pytest.mark.parametrize("mechanism", ["host-native", "execution-flow"]) -def test_sg08_ownership_is_mechanism_neutral(mechanism: str) -> None: - result = TaskOwnershipScheduler(RecordingAdapter(mechanism=mechanism)).run_wave( - [TaskCandidate("task-001", (), ("src/a.py",), "workspace-a")], completed=set() +def test_sg07_repair_allows_only_an_authorized_replacement_owner() -> None: + class ReplacementAdapter(RecordingAdapter): + def dispatch(self, task: TaskCandidate, *, operation: str) -> SubagentDispatch: + replacement = {**provenance(), "agent_id": "replacement-agent", "run_id": "repair-run"} + return SubagentDispatch(task.task_id, replacement) + + task = TaskCandidate("task-001", (), ("src/a.py",), "workspace-a") + continuity = RepairContinuity("binding", "baseline", "evidence", "review-head") + scheduler = TaskOwnershipScheduler(ReplacementAdapter()) + with pytest.raises(OwnershipBlocker, match="review-blocked"): + scheduler.run_wave( + [task], completed=set(), operation="repair", + prior_ownership={"task-001": provenance()}, + repair_continuity={"task-001": continuity}, + ) + accepted = scheduler.run_wave( + [task], completed=set(), operation="repair", + prior_ownership={"task-001": provenance()}, + repair_continuity={"task-001": continuity}, + authorized_replacements={"task-001"}, ) - assert result.ownership[0]["mechanism"] == mechanism - assert set(result.ownership[0]) == {"delegated", "owner_kind", "agent_id", "run_id", "mechanism"} + assert accepted.repair_continuity == (continuity,) + + +def test_sg08_native_mechanisms_preserve_task_binding_handoff_and_validation_semantics() -> None: + observed: list[tuple[object, ...]] = [] + for mechanism in ("host-native", "execution-flow"): + adapter = HeldOpenAdapter(expected_dispatches=1) + adapter.mechanism = mechanism + task = TaskCandidate("task-001", (), ("src/a.py",), "workspace-a", binding_id="binding-001") + scheduler = TaskOwnershipScheduler(adapter) + result = scheduler.run_wave([task], completed=set()) + validated = scheduler.validate_acceptance( + delegation_evidence=result.ownership[0], mutation_events=[], + write_scope=task.write_scope, validations_passed=True, + ) + observed.append( + (result.dispatched, task.binding_id, result.results, validated["owner_kind"], set(validated)) + ) + assert result.ownership[0]["mechanism"] == mechanism + assert observed[0] == observed[1] def test_visibility_specific_provenance_is_rejected() -> None: From 58c3f23c17a5cc33a56e618f82c0284c1c19d594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 04:30:36 +0800 Subject: [PATCH 12/36] fix(orchestration): enforce task ownership at acceptance --- scripts/orchestration/execution_context.py | 25 ++++ tests/test_orchestration_execution_context.py | 13 ++ .../test_orchestration_workflow_contracts.py | 7 + tests/test_wor108_context_projection.py | 124 ++++++++++++++++++ 4 files changed, 169 insertions(+) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 2c0b747..268d947 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -19,6 +19,7 @@ from artifact_inputs import (_split_top_level, _split_key_value, _parse_scalar, parse_yaml_subset, _read_structured, _as_list, _input_path, _resolve_spec_paths) from repository_preflight import capture_repository_evidence, task_caused_paths +from task_ownership import OwnershipBlocker, validate_task_acceptance_ownership SOURCE_ID_TOKEN = r"[A-Z][A-Z0-9_-]*-\d+[A-Z]?" @@ -1884,12 +1885,36 @@ def validate_executor_result_for_task( raise SystemExit( "evidence-closure-blocked: completed mapped invariants require produced harness observations and passed evidence closure" ) + task_ownership = None + if state == "completed": + mutation_events = handoff.get("mutation_events", []) + if not isinstance(mutation_events, list) or any( + not isinstance(event, dict) for event in mutation_events + ): + raise SystemExit("Executor result mutation_events must be a list of mappings") + fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} + operation = "repair" if fit.get("result") == "repaired" else "implementation" + try: + task_ownership = validate_task_acceptance_ownership( + delegation_evidence=( + handoff.get("delegation_evidence") + if isinstance(handoff.get("delegation_evidence"), dict) + else None + ), + mutation_events=mutation_events, + write_scope=_as_list(task_files.get("write")), + validations_passed=True, + operation=operation, + ) + except OwnershipBlocker as error: + raise SystemExit(str(error)) from error return { "knowledge_disposition": knowledge_disposition, "unresolved": unresolved, "result_state": state, "evidence_applicability": evidence_applicability, "evidence_closure": evidence_closure, + **({"task_ownership": task_ownership} if task_ownership is not None else {}), **({"observed_validation": observed_validation} if observed_validation is not None else {}), } diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 6fa8b77..4260a18 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -714,6 +714,12 @@ def evidence_blocks(root: Path, *, codegraph: str = "no-index") -> str: "codegraph:\n" f" - root: {root.resolve()}\n" f"{codegraph_block}" + "delegation_evidence:\n" + " delegated: true\n" + " owner_kind: subagent\n" + " agent_id: execution-context-fixture-agent\n" + " run_id: execution-context-fixture-run\n" + " mechanism: host-native\n" ) @@ -2398,6 +2404,13 @@ def _repair_completion_fixture() -> tuple[dict, dict]: "result": {"state": "completed"}, "task_fit_check": {"task": "task-rf", "result": "repaired"}, "acceptance_review": {"required": True, "verdict": "accept"}, + "delegation_evidence": { + "delegated": True, + "owner_kind": "subagent", + "agent_id": "repair-fixture-agent", + "run_id": "repair-fixture-run", + "mechanism": "host-native", + }, "knowledge_disposition": {"action": "none", "reason": "No authority change.", "affected_authority": []}, } return brief, handoff diff --git a/tests/test_orchestration_workflow_contracts.py b/tests/test_orchestration_workflow_contracts.py index 26f61e8..c44eb47 100644 --- a/tests/test_orchestration_workflow_contracts.py +++ b/tests/test_orchestration_workflow_contracts.py @@ -90,6 +90,13 @@ def _completed_executor_result( "related": related, "result": {"state": "completed"}, "task_fit_check": {"task": task, "result": "clean"}, + "delegation_evidence": { + "delegated": True, + "owner_kind": "subagent", + "agent_id": "workflow-contract-fixture-agent", + "run_id": "workflow-contract-fixture-run", + "mechanism": "host-native", + }, "knowledge_disposition": { "action": "none", "reason": "No stable authority changed.", diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index d2bee86..789c375 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -1,5 +1,6 @@ from __future__ import annotations +import builtins import json import sys from copy import deepcopy @@ -34,6 +35,31 @@ def _document(root: Path, task: Path) -> dict: return execution_context._read_structured(build_task_brief(args(root, task)))[0] +def _delegation_evidence() -> dict[str, object]: + return { + "delegated": True, + "owner_kind": "subagent", + "agent_id": "ctx-fixture-agent", + "run_id": "ctx-fixture-run", + "mechanism": "host-native", + } + + +def _without_terminal_evidence(brief: dict[str, object], reason: str) -> dict[str, object]: + projected = deepcopy(brief) + projected["validation"] = [] + projected["evidence_capability"] = { + "result": "no_validation_bearing_obligation", + "reason": reason, + "invariants": [], + } + projected["evidence_applicability"] = { + kind: {"required": False, "reasons": []} + for kind in ("metadata", "repository", "codegraph") + } + return projected + + def test_ctx_01_unrelated_runtime_history_does_not_inflate_unchanged_task_brief( tmp_path: Path, ) -> None: @@ -192,6 +218,7 @@ def test_ctx_05_failed_evidence_expands_only_with_allowed_reason() -> None: def test_ctx_06_no_retrieval_escape_hatch_and_context_metrics_use_existing_telemetry( tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, ) -> None: root, _, task = workspace(tmp_path) document = _document(root, task) @@ -207,6 +234,54 @@ def test_ctx_06_no_retrieval_escape_hatch_and_context_metrics_use_existing_telem assert metrics["expansion_reason"] is None assert "hard_limit" not in json.dumps(document) + compiled_packet = _without_terminal_evidence( + brief, "CTX-06 executes only from its already-compiled packet." + ) + handoff = { + "type": "executor-result", + "related": {"plan": brief["plan_id"], "task": brief["task_id"]}, + "result": {"state": "completed"}, + "task_fit_check": {"task": brief["task_id"], "result": "clean"}, + "delegation_evidence": _delegation_evidence(), + "knowledge_disposition": { + "action": "none", + "reason": "No stable authority changed.", + "affected_authority": [], + }, + } + original_open = builtins.open + original_read_text = Path.read_text + original_read_bytes = Path.read_bytes + + def retrieval_is_denied(file: object) -> bool: + candidate = str(file) + return ( + ".work-bundle/knowledge" in candidate + or ".work-bundle/orchestration" in candidate + ) + + def deny_runtime_retrieval(file: object, *args: object, **kwargs: object): + if retrieval_is_denied(file): + raise AssertionError(f"executor retrieval attempted: {file}") + return original_open(file, *args, **kwargs) + + def deny_path_text(file: Path, *args: object, **kwargs: object) -> str: + if retrieval_is_denied(file): + raise AssertionError(f"executor retrieval attempted: {file}") + return original_read_text(file, *args, **kwargs) + + def deny_path_bytes(file: Path) -> bytes: + if retrieval_is_denied(file): + raise AssertionError(f"executor retrieval attempted: {file}") + return original_read_bytes(file) + + monkeypatch.setattr(builtins, "open", deny_runtime_retrieval) + monkeypatch.setattr(Path, "read_text", deny_path_text) + monkeypatch.setattr(Path, "read_bytes", deny_path_bytes) + accepted = execution_context.validate_executor_result_for_task(handoff, compiled_packet) + assert accepted["result_state"] == "completed" + assert accepted["task_ownership"]["agent_id"] == "ctx-fixture-agent" + payload = event() payload["compiled_context_metrics"] = metrics assert validate_stage_event(payload).compiled_context_metrics == metrics @@ -216,6 +291,55 @@ def test_ctx_06_no_retrieval_escape_hatch_and_context_metrics_use_existing_telem validate_stage_event(invalid) +def test_completed_task_acceptance_requires_subagent_delegation_evidence(tmp_path: Path) -> None: + root, _, task = workspace(tmp_path) + brief = _without_terminal_evidence( + _document(root, task)["task_brief"], "Ownership-only acceptance fixture." + ) + handoff = { + "type": "executor-result", + "related": {"plan": brief["plan_id"], "task": brief["task_id"]}, + "result": {"state": "completed"}, + "task_fit_check": {"task": brief["task_id"], "result": "clean"}, + "knowledge_disposition": { + "action": "none", + "reason": "No stable authority changed.", + "affected_authority": [], + }, + } + + with pytest.raises(SystemExit, match="workspace-blocked.*subagent ownership"): + execution_context.validate_executor_result_for_task(handoff, brief) + + +def test_completed_task_acceptance_rejects_controller_task_scope_mutation( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + brief = _without_terminal_evidence( + _document(root, task)["task_brief"], "Ownership-only acceptance fixture." + ) + handoff = { + "type": "executor-result", + "related": {"plan": brief["plan_id"], "task": brief["task_id"]}, + "result": {"state": "completed"}, + "task_fit_check": {"task": brief["task_id"], "result": "clean"}, + "delegation_evidence": _delegation_evidence(), + "mutation_events": [{ + "actor_kind": "controller", + "paths": [brief["files"]["write"][0]], + }], + "knowledge_disposition": { + "action": "none", + "reason": "No stable authority changed.", + "affected_authority": [], + }, + } + + with pytest.raises(SystemExit, match="review-blocked.*controller mutated"): + execution_context.validate_executor_result_for_task(handoff, brief) + + def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( tmp_path: Path, ) -> None: From fa663f0ca717a21fe89904e11727ca5a230d7f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 04:45:47 +0800 Subject: [PATCH 13/36] fix(orchestration): keep mutation events runtime-only --- scripts/orchestration/execution_context.py | 14 +++++++------- tests/test_wor108_context_projection.py | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 268d947..f0d7ae5 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -11,7 +11,7 @@ import subprocess import sys from pathlib import Path -from typing import Any +from typing import Any, Iterable, Mapping from datetime import datetime, timezone @@ -94,6 +94,7 @@ def __reduce__(self): "strategy_advice", "knowledge_persistence", "baseline", + "mutation_events", } VALID_RESULT_STATES = {"completed", "blocked", "partial", "failed"} TASK_FIT_RESULTS = {"clean", "repaired", "unresolved", "skipped"} @@ -1768,6 +1769,7 @@ def validate_executor_result_for_task( execution_id: str | None = None, repository_id: str | None = None, execution_runtime_root: str | None = None, + mutation_events: Iterable[Mapping[str, object]] | None = None, ) -> dict[str, Any]: if handoff.get("type") != "executor-result": raise SystemExit("Handoff is not executor-result") @@ -1887,11 +1889,9 @@ def validate_executor_result_for_task( ) task_ownership = None if state == "completed": - mutation_events = handoff.get("mutation_events", []) - if not isinstance(mutation_events, list) or any( - not isinstance(event, dict) for event in mutation_events - ): - raise SystemExit("Executor result mutation_events must be a list of mappings") + runtime_mutation_events = list(mutation_events or []) + if any(not isinstance(event, Mapping) for event in runtime_mutation_events): + raise SystemExit("Runtime mutation_events must contain mappings") fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} operation = "repair" if fit.get("result") == "repaired" else "implementation" try: @@ -1901,7 +1901,7 @@ def validate_executor_result_for_task( if isinstance(handoff.get("delegation_evidence"), dict) else None ), - mutation_events=mutation_events, + mutation_events=runtime_mutation_events, write_scope=_as_list(task_files.get("write")), validations_passed=True, operation=operation, diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 789c375..5411b5e 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -325,10 +325,6 @@ def test_completed_task_acceptance_rejects_controller_task_scope_mutation( "result": {"state": "completed"}, "task_fit_check": {"task": brief["task_id"], "result": "clean"}, "delegation_evidence": _delegation_evidence(), - "mutation_events": [{ - "actor_kind": "controller", - "paths": [brief["files"]["write"][0]], - }], "knowledge_disposition": { "action": "none", "reason": "No stable authority changed.", @@ -337,7 +333,18 @@ def test_completed_task_acceptance_rejects_controller_task_scope_mutation( } with pytest.raises(SystemExit, match="review-blocked.*controller mutated"): - execution_context.validate_executor_result_for_task(handoff, brief) + execution_context.validate_executor_result_for_task( + handoff, + brief, + mutation_events=[{ + "actor_kind": "controller", + "paths": [brief["files"]["write"][0]], + }], + ) + assert "mutation_events" not in handoff + durable_history = {**handoff, "mutation_events": []} + with pytest.raises(SystemExit, match="forbidden field mutation_events"): + execution_context.validate_executor_result_for_task(durable_history, brief) def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( From cc19c6878b42205505f1215a35c8a17b96971143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 04:58:21 +0800 Subject: [PATCH 14/36] fix(orchestration): admit exact dependency deltas --- scripts/orchestration/execution_context.py | 129 ++++++++++++++++++-- tests/test_wor108_context_projection.py | 132 +++++++++++++++++++++ 2 files changed, 253 insertions(+), 8 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index f0d7ae5..0e9a53e 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -95,6 +95,7 @@ def __reduce__(self): "knowledge_persistence", "baseline", "mutation_events", + "accepted_dependency_deltas", } VALID_RESULT_STATES = {"completed", "blocked", "partial", "failed"} TASK_FIT_RESULTS = {"clean", "repaired", "unresolved", "skipped"} @@ -1479,14 +1480,104 @@ def _path_is_forbidden(relative: str, forbidden: list[str]) -> bool: def _assert_task_caused_delta_in_write_scope( caused: list[str], task_files: dict[str, Any], + *, + accepted_dependency_paths: set[str] | None = None, ) -> None: write_paths = [str(path) for path in _as_list(task_files.get("write"))] forbidden = [str(path) for path in _as_list(task_files.get("forbidden"))] for relative in caused: + if relative in (accepted_dependency_paths or set()): + continue if _path_is_forbidden(relative, forbidden) or not _write_scope_match(relative, write_paths): raise SystemExit(f"Unauthorized task-caused delta outside write scope: {relative}") +def _accepted_dependency_paths( + task: dict[str, Any], + execution_root: Path, + accepted_dependency_deltas: Iterable[Mapping[str, object]] | None, +) -> set[str]: + descriptors = list(accepted_dependency_deltas or []) + if not descriptors: + return set() + dependencies = {str(value) for value in _as_list(task.get("depends_on"))} + workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} + control_root = Path(str(workspace.get("root") or "")).resolve() + handoff_root = control_root / ".work-bundle/orchestration/handoff" + admitted: set[str] = set() + required_fields = { + "task_id", "handoff_id", "handoff_sha256", "integrated_base", "integrated_head" + } + for descriptor in descriptors: + if not isinstance(descriptor, Mapping) or set(descriptor) != required_fields: + raise SystemExit("accepted_dependency_deltas entries must use the closed runtime identity shape") + dependency_id = str(descriptor["task_id"]) + handoff_id = str(descriptor["handoff_id"]) + if dependency_id not in dependencies: + raise SystemExit(f"accepted_dependency_deltas names undeclared dependency: {dependency_id}") + matches: list[tuple[Path, dict[str, Any]]] = [] + for path in sorted(handoff_root.glob("executor/*/*")): + if not path.is_file() or path.is_symlink(): + continue + try: + document, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + if document.get("id") == handoff_id: + matches.append((path, document)) + if len(matches) != 1: + raise SystemExit(f"accepted dependency handoff identity is missing or ambiguous: {handoff_id}") + handoff_path, handoff = matches[0] + actual_digest = hashlib.sha256(handoff_path.read_bytes()).hexdigest() + if actual_digest != str(descriptor["handoff_sha256"]): + raise SystemExit(f"accepted dependency handoff identity is stale: {handoff_id}") + related = handoff.get("related") if isinstance(handoff.get("related"), dict) else {} + result = handoff.get("result") if isinstance(handoff.get("result"), dict) else {} + review = handoff.get("acceptance_review") if isinstance(handoff.get("acceptance_review"), dict) else {} + frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), dict) else {} + previous = frontier.get("previous_reviewed_identity") if isinstance(frontier.get("previous_reviewed_identity"), dict) else {} + repaired = frontier.get("repaired_identity") if isinstance(frontier.get("repaired_identity"), dict) else {} + if (related.get("task") != dependency_id or result.get("state") != "completed" + or review.get("verdict") != "accept" or review.get("review_mode") != "repair" + or repaired != review.get("target_identity")): + raise SystemExit(f"dependency handoff is not an accepted repair result: {handoff_id}") + source_base = str(previous.get("revision") or "") + source_head = str(repaired.get("revision") or "") + integrated_base = _resolve_commit(execution_root, str(descriptor["integrated_base"])) + integrated_head = _resolve_commit(execution_root, str(descriptor["integrated_head"])) + current_head = _resolve_commit(execution_root, "HEAD") + if not source_base or not source_head: + raise SystemExit(f"accepted dependency repair identity is incomplete: {handoff_id}") + for commit, identity in ((source_base, previous), (source_head, repaired)): + resolved = _resolve_commit(execution_root, commit) + tree = _git(execution_root, "rev-parse", f"{resolved}^{{tree}}").strip() + if tree != identity.get("source_tree"): + raise SystemExit(f"accepted dependency Git identity is mismatched: {handoff_id}") + if subprocess.run( + ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", integrated_head, current_head], + capture_output=True, + check=False, + ).returncode: + raise SystemExit(f"accepted dependency integration checkpoint is not current: {handoff_id}") + source_diff = _git(execution_root, "diff", "--binary", source_base, source_head, "--") + integrated_diff = _git( + execution_root, "diff", "--binary", integrated_base, integrated_head, "--" + ) + if source_diff != integrated_diff: + raise SystemExit(f"accepted dependency integration checkpoint is mismatched: {handoff_id}") + paths = { + path + for line in _git(execution_root, "diff", "--name-status", source_base, source_head, "--").splitlines() + for path in _paths_from_name_status(line) + } + if paths and _git( + execution_root, "diff", "--name-only", integrated_head, "--", *sorted(paths) + ).strip(): + raise SystemExit(f"accepted dependency path changed after integration: {handoff_id}") + admitted.update(paths) + return admitted + + def _observe_completed_validation( handoff: dict[str, Any], task: dict[str, Any], @@ -1497,6 +1588,7 @@ def _observe_completed_validation( execution_id: str | None = None, repository_id: str | None = None, execution_runtime_root: str | None = None, + accepted_dependency_deltas: Iterable[Mapping[str, object]] | None = None, ) -> list[dict[str, Any]]: if "harness_receipt" in handoff or ( isinstance(handoff.get("validation"), dict) and "harness_receipt" in handoff["validation"] @@ -1529,7 +1621,12 @@ def _observe_completed_validation( raise SystemExit(str(error)) from error observed_items: list[dict[str, Any]] = [] task_files = task.get("files") if isinstance(task.get("files"), dict) else {} - _assert_task_caused_delta_in_write_scope(task_caused_paths(baseline, pre_batch, execution_root), task_files) + accepted_paths = _accepted_dependency_paths(task, execution_root, accepted_dependency_deltas) + _assert_task_caused_delta_in_write_scope( + task_caused_paths(baseline, pre_batch, execution_root), + task_files, + accepted_dependency_paths=accepted_paths, + ) for item in required_items: observed = _completion_provenance_module().observe_validation( binding, task, item, pre_batch, @@ -1561,7 +1658,9 @@ def _observe_completed_validation( ) caused = task_caused_paths(baseline, post_batch, execution_root) task_files = task.get("files") if isinstance(task.get("files"), dict) else {} - _assert_task_caused_delta_in_write_scope(caused, task_files) + _assert_task_caused_delta_in_write_scope( + caused, task_files, accepted_dependency_paths=accepted_paths + ) if binding.get("mutating") is True: updated = dict(binding) updated["mutating"] = False @@ -1660,6 +1759,7 @@ def _observe_repository_and_codegraph_evidence( codegraph_entries: list[dict[str, Any]], *, codegraph_required: bool, + accepted_dependency_deltas: Iterable[Mapping[str, object]] | None = None, ) -> None: workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} control_root_raw = workspace.get("root") @@ -1684,7 +1784,13 @@ def _observe_repository_and_codegraph_evidence( raise SystemExit("Task execution binding is missing harness provenance baseline") caused = task_caused_paths(baseline, observed_repository, execution_root) task_files = task.get("files") if isinstance(task.get("files"), dict) else {} - _assert_task_caused_delta_in_write_scope(caused, task_files) + _assert_task_caused_delta_in_write_scope( + caused, + task_files, + accepted_dependency_paths=_accepted_dependency_paths( + task, execution_root, accepted_dependency_deltas + ), + ) metadata = repository.get("metadata") if isinstance(metadata, dict): @@ -1770,6 +1876,7 @@ def validate_executor_result_for_task( repository_id: str | None = None, execution_runtime_root: str | None = None, mutation_events: Iterable[Mapping[str, object]] | None = None, + accepted_dependency_deltas: Iterable[Mapping[str, object]] | None = None, ) -> dict[str, Any]: if handoff.get("type") != "executor-result": raise SystemExit("Handoff is not executor-result") @@ -1863,10 +1970,11 @@ def validate_executor_result_for_task( if observe and (repository_entries or codegraph_entries): _observe_repository_and_codegraph_evidence( task, - repository_entries, - codegraph_entries, - codegraph_required=evidence_applicability["codegraph"]["required"], - ) + repository_entries, + codegraph_entries, + codegraph_required=evidence_applicability["codegraph"]["required"], + accepted_dependency_deltas=accepted_dependency_deltas, + ) if state == "completed" and required_items and observe: observed_validation = _observe_completed_validation( @@ -1878,6 +1986,7 @@ def validate_executor_result_for_task( execution_id=execution_id, repository_id=repository_id, execution_runtime_root=execution_runtime_root, + accepted_dependency_deltas=accepted_dependency_deltas, ) evidence_closure = _validate_evidence_closure( handoff, task, state, reported_commands, observed_validation @@ -2242,6 +2351,7 @@ def _compile_task_brief(args: argparse.Namespace) -> tuple[Path, dict[str, Any]] task_brief = { "task_id": task_id, "plan_id": plan_id, + "depends_on": [str(value) for value in _as_list(task.get("depends_on"))], "source_ids": source_ids, "goal": resolved_goal, "truth_basis": truth_basis, @@ -2662,12 +2772,15 @@ def cmd_validate_executor_result(args: argparse.Namespace) -> None: print(handoff_path.relative_to(root).as_posix()) -def _observation_kwargs(args: argparse.Namespace) -> dict[str, str | None]: +def _observation_kwargs(args: argparse.Namespace) -> dict[str, Any]: return { "workspace_id": getattr(args, "workspace_id", None) or None, "execution_id": getattr(args, "execution_id", None) or None, "repository_id": getattr(args, "repository_id", None) or None, "execution_runtime_root": getattr(args, "execution_runtime_root", None) or None, + "accepted_dependency_deltas": ( + getattr(args, "accepted_dependency_deltas", None) or None + ), } diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 5411b5e..24ef5e2 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -1,6 +1,7 @@ from __future__ import annotations import builtins +import hashlib import json import sys from copy import deepcopy @@ -347,6 +348,137 @@ def test_completed_task_acceptance_rejects_controller_task_scope_mutation( execution_context.validate_executor_result_for_task(durable_history, brief) +def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + scoped = _ensure_source_file(root) + dependency = root / "references/assets/orchestration/workflow.md" + dependency.parent.mkdir(parents=True, exist_ok=True) + dependency.write_text("before\n", encoding="utf-8") + git(root, "add", ".") + git(root, "commit", "-qm", "baseline") + baseline = git(root, "rev-parse", "HEAD") + baseline_tree = git(root, "rev-parse", "HEAD^{tree}") + + brief = _document(root, task)["task_brief"] + brief["depends_on"] = ["task-dependency"] + _bind_task_execution(root, brief) + + dependency.write_text("accepted dependency\n", encoding="utf-8") + git(root, "add", str(dependency.relative_to(root))) + git(root, "commit", "-qm", "accepted dependency") + checkpoint = git(root, "rev-parse", "HEAD") + checkpoint_tree = git(root, "rev-parse", "HEAD^{tree}") + scoped.write_text("def compile_task():\n return 'task repair'\n", encoding="utf-8") + git(root, "add", str(scoped.relative_to(root))) + git(root, "commit", "-qm", "task repair") + + identity = lambda commit, tree: { + "artifact_id": "task-dependency", + "revision": commit, + "sha256": execution_context.semantic_digest({"commit": commit, "tree": tree}), + "source_tree": tree, + } + dependency_handoff = { + "id": "handoff-accepted-dependency", + "type": "executor-result", + "related": {"plan": brief["plan_id"], "task": "task-dependency"}, + "result": {"state": "completed"}, + "acceptance_review": { + "required": True, + "verdict": "accept", + "review_mode": "repair", + "target_identity": identity(checkpoint, checkpoint_tree), + "repair_frontier": { + "previous_reviewed_identity": identity(baseline, baseline_tree), + "repaired_identity": identity(checkpoint, checkpoint_tree), + }, + }, + } + accepted_path = ( + root + / ".work-bundle/orchestration/handoff/executor/active/handoff-accepted-dependency.yaml" + ) + accepted_path.parent.mkdir(parents=True, exist_ok=True) + accepted_path.write_text( + "\n".join(execution_context._dump_yaml(dependency_handoff)) + "\n", + encoding="utf-8", + ) + descriptor = { + "task_id": "task-dependency", + "handoff_id": "handoff-accepted-dependency", + "handoff_sha256": hashlib.sha256(accepted_path.read_bytes()).hexdigest(), + "integrated_base": baseline, + "integrated_head": checkpoint, + } + + current = _without_terminal_evidence(brief, "Dependency attribution fixture.") + current["evidence_applicability"] = { + "metadata": {"required": False, "reasons": []}, + "repository": {"required": True, "reasons": ["accepted dependency delta"]}, + "codegraph": {"required": False, "reasons": []}, + } + handoff = { + "type": "executor-result", + "related": {"plan": brief["plan_id"], "task": brief["task_id"]}, + "result": {"state": "completed"}, + "task_fit_check": {"task": brief["task_id"], "result": "clean"}, + "delegation_evidence": _delegation_evidence(), + "repository": [{ + "root": str(root.resolve()), + "target_kind": "git-backed", + "preflight_kind": "git-clean-worktree", + "baseline": "initial", + "status": "clean", + }], + "knowledge_disposition": { + "action": "none", + "reason": "No stable authority changed.", + "affected_authority": [], + }, + } + + with pytest.raises(SystemExit, match="workflow.md"): + execution_context.validate_executor_result_for_task(handoff, current, observe=True) + accepted = execution_context.validate_executor_result_for_task( + handoff, current, observe=True, accepted_dependency_deltas=[descriptor] + ) + assert accepted["result_state"] == "completed" + + stale = {**descriptor, "handoff_sha256": "0" * 64} + with pytest.raises(SystemExit, match="handoff identity is stale"): + execution_context.validate_executor_result_for_task( + handoff, current, observe=True, accepted_dependency_deltas=[stale] + ) + mismatched = {**descriptor, "integrated_head": git(root, "rev-parse", "HEAD")} + with pytest.raises(SystemExit, match="checkpoint is mismatched"): + execution_context.validate_executor_result_for_task( + handoff, current, observe=True, accepted_dependency_deltas=[mismatched] + ) + accepted_bytes = accepted_path.read_bytes() + unaccepted_handoff = deepcopy(dependency_handoff) + unaccepted_handoff["acceptance_review"]["verdict"] = "pending" + accepted_path.write_text( + "\n".join(execution_context._dump_yaml(unaccepted_handoff)) + "\n", + encoding="utf-8", + ) + unaccepted = { + **descriptor, + "handoff_sha256": hashlib.sha256(accepted_path.read_bytes()).hexdigest(), + } + with pytest.raises(SystemExit, match="not an accepted repair result"): + execution_context.validate_executor_result_for_task( + handoff, current, observe=True, accepted_dependency_deltas=[unaccepted] + ) + accepted_path.write_bytes(accepted_bytes) + dependency.write_text("later task mutation\n", encoding="utf-8") + with pytest.raises(SystemExit, match="changed after integration"): + execution_context.validate_executor_result_for_task( + handoff, current, observe=True, accepted_dependency_deltas=[descriptor] + ) + + def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( tmp_path: Path, ) -> None: From 39fff065d448f54e789e7070b09f93cc654bfb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 05:23:39 +0800 Subject: [PATCH 15/36] fix(orchestration): enforce repair runtime ownership --- scripts/orchestration/dispatcher.py | 20 ++++ scripts/orchestration/execution_context.py | 113 +++++++++++++++++- tests/test_orchestration_execution_context.py | 72 ++++++++++- tests/test_wor108_context_projection.py | 79 ++++++++++++ 4 files changed, 278 insertions(+), 6 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 7742edf..1fc5efd 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse +import json from core import HANDOFF_TYPES from doctor import cmd_doctor @@ -23,6 +24,23 @@ }) +def _runtime_json(value: str) -> object: + try: + return json.loads(value) + except json.JSONDecodeError as error: + raise argparse.ArgumentTypeError(f"invalid controller runtime JSON: {error.msg}") from error + + +def _add_acceptance_runtime_inputs(parser: argparse.ArgumentParser) -> None: + """Expose harness observations without adding them to durable executor results.""" + + parser.add_argument("--mutation-events", type=_runtime_json) + parser.add_argument("--accepted-dependency-deltas", type=_runtime_json) + parser.add_argument("--prior-ownership", type=_runtime_json) + parser.add_argument("--repair-continuity", type=_runtime_json) + parser.add_argument("--authorized-replacements", type=_runtime_json) + + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser() parser.add_argument("--project-root") @@ -59,6 +77,7 @@ def build_parser() -> argparse.ArgumentParser: validate_result = sub.add_parser("validate-executor-result", parents=[parent]) validate_result.add_argument("--task", required=True) validate_result.add_argument("--handoff", required=True) + _add_acceptance_runtime_inputs(validate_result) validate_result.set_defaults(func=cmd_validate_executor_result) observe_validation = sub.add_parser("observe-task-validation", parents=[parent]) observe_validation.add_argument("--task", required=True) @@ -108,6 +127,7 @@ def build_parser() -> argparse.ArgumentParser: set_plan.add_argument("--kind", choices=["plan", "phase", "task"]) set_plan.add_argument("--plan-id") set_plan.add_argument("--handoff") + _add_acceptance_runtime_inputs(set_plan) set_plan.set_defaults(func=cmd_set_plan_status) archive_plan = sub.add_parser("archive-plan", parents=[parent]) archive_plan.add_argument("--id", required=True) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 0e9a53e..33b9ce7 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -19,7 +19,12 @@ from artifact_inputs import (_split_top_level, _split_key_value, _parse_scalar, parse_yaml_subset, _read_structured, _as_list, _input_path, _resolve_spec_paths) from repository_preflight import capture_repository_evidence, task_caused_paths -from task_ownership import OwnershipBlocker, validate_task_acceptance_ownership +from task_ownership import ( + OwnershipBlocker, + RepairContinuity, + normalize_subagent_provenance, + validate_task_acceptance_ownership, +) SOURCE_ID_TOKEN = r"[A-Z][A-Z0-9_-]*-\d+[A-Z]?" @@ -1877,6 +1882,9 @@ def validate_executor_result_for_task( execution_runtime_root: str | None = None, mutation_events: Iterable[Mapping[str, object]] | None = None, accepted_dependency_deltas: Iterable[Mapping[str, object]] | None = None, + prior_ownership: Mapping[str, Mapping[str, object]] | None = None, + repair_continuity: Mapping[str, Mapping[str, object] | RepairContinuity] | None = None, + authorized_replacements: Iterable[str] | None = None, ) -> dict[str, Any]: if handoff.get("type") != "executor-result": raise SystemExit("Handoff is not executor-result") @@ -2015,6 +2023,15 @@ def validate_executor_result_for_task( validations_passed=True, operation=operation, ) + if operation == "repair": + _validate_repair_acceptance_continuity( + task=task, + handoff=handoff, + current_ownership=task_ownership, + prior_ownership=prior_ownership, + repair_continuity=repair_continuity, + authorized_replacements=authorized_replacements, + ) except OwnershipBlocker as error: raise SystemExit(str(error)) from error return { @@ -2028,6 +2045,78 @@ def validate_executor_result_for_task( } +def _validate_repair_acceptance_continuity( + *, + task: Mapping[str, object], + handoff: Mapping[str, object], + current_ownership: Mapping[str, object], + prior_ownership: Mapping[str, Mapping[str, object]] | None, + repair_continuity: Mapping[str, Mapping[str, object] | RepairContinuity] | None, + authorized_replacements: Iterable[str] | None, +) -> None: + """Bind repair acceptance to the scheduler's original owner and identities.""" + + task_id = str(task.get("task_id") or "") + if prior_ownership is not None and not isinstance(prior_ownership, Mapping): + raise OwnershipBlocker("review-blocked", "repair prior_ownership must be a task mapping") + if repair_continuity is not None and not isinstance(repair_continuity, Mapping): + raise OwnershipBlocker("review-blocked", "repair_continuity must be a task mapping") + previous_value = (prior_ownership or {}).get(task_id) + continuity_value = (repair_continuity or {}).get(task_id) + if previous_value is None: + raise OwnershipBlocker("review-blocked", f"{task_id} repair lacks prior owner") + if continuity_value is None: + raise OwnershipBlocker("review-blocked", f"{task_id} repair lacks continuity identities") + previous = normalize_subagent_provenance(previous_value) + if isinstance(continuity_value, RepairContinuity): + continuity = continuity_value + else: + if not isinstance(continuity_value, Mapping) or set(continuity_value) != { + "binding_id", + "baseline_identity", + "evidence_identity", + "previous_review_identity", + }: + raise OwnershipBlocker("review-blocked", f"{task_id} repair continuity is not closed") + try: + continuity = RepairContinuity(**{key: str(value) for key, value in continuity_value.items()}) + except (TypeError, ValueError) as error: + raise OwnershipBlocker("review-blocked", f"{task_id} repair continuity is invalid") from error + + workspace = task.get("workspace") if isinstance(task.get("workspace"), Mapping) else {} + control_root = Path(str(workspace.get("root") or "")).expanduser().resolve() + if not control_root.is_dir(): + raise OwnershipBlocker("review-blocked", f"{task_id} repair control root is unavailable") + binding = load_task_execution_binding(control_root, str(task.get("plan_id") or ""), task_id) + baseline = binding.get("baseline") + if not isinstance(baseline, Mapping) or not baseline.get("head"): + raise OwnershipBlocker("review-blocked", f"{task_id} repair baseline identity is unavailable") + review = handoff.get("acceptance_review") if isinstance(handoff.get("acceptance_review"), Mapping) else {} + frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), Mapping) else {} + ownership = binding.get("ownership") if isinstance(binding.get("ownership"), Mapping) else {} + try: + expected = RepairContinuity( + binding_id=str(ownership.get("binding_id") or ""), + baseline_identity=semantic_digest(dict(baseline)), + evidence_identity=str(frontier.get("frozen_evidence_reference") or ""), + previous_review_identity=str(frontier.get("prior_review_id") or ""), + ) + except ValueError as error: + raise OwnershipBlocker( + "review-blocked", f"{task_id} repair frontier continuity is unavailable" + ) from error + if continuity != expected: + raise OwnershipBlocker("review-blocked", f"{task_id} repair continuity identities do not match") + if isinstance(authorized_replacements, (str, bytes, Mapping)): + raise OwnershipBlocker("review-blocked", "authorized_replacements must be task IDs") + replacements = {str(value) for value in (authorized_replacements or [])} + replaced = current_ownership.get("agent_id") != previous.get("agent_id") + if replaced and task_id not in replacements: + raise OwnershipBlocker( + "review-blocked", f"{task_id} repair owner replacement is not authorized" + ) + + def _acceptable_validation_results(item: dict[str, Any]) -> set[str]: raw = item.get("acceptable_results") if isinstance(raw, list) and raw: @@ -2755,7 +2844,23 @@ def cmd_build_review_package(args: argparse.Namespace) -> None: def cmd_observe_task_validation(args: argparse.Namespace) -> None: _, brief_document = _compile_task_brief(args) task = brief_document["task_brief"] - observed = _observe_completed_validation({}, task, task["validation"], None, **_observation_kwargs(args)) + runtime = _observation_kwargs(args) + observed = _observe_completed_validation( + {}, + task, + task["validation"], + None, + **{ + key: runtime[key] + for key in ( + "workspace_id", + "execution_id", + "repository_id", + "execution_runtime_root", + "accepted_dependency_deltas", + ) + }, + ) print(json.dumps({"validation": observed}, sort_keys=True)) @@ -2781,6 +2886,10 @@ def _observation_kwargs(args: argparse.Namespace) -> dict[str, Any]: "accepted_dependency_deltas": ( getattr(args, "accepted_dependency_deltas", None) or None ), + "mutation_events": getattr(args, "mutation_events", None) or None, + "prior_ownership": getattr(args, "prior_ownership", None) or None, + "repair_continuity": getattr(args, "repair_continuity", None) or None, + "authorized_replacements": getattr(args, "authorized_replacements", None) or None, } diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 4260a18..bef8ef1 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -244,6 +244,70 @@ def test_observe_task_validation_is_dispatched() -> None: assert "--task" in completed.stdout +@pytest.mark.parametrize("command", ["validate-executor-result", "set-plan-status"]) +def test_normal_cli_projects_controller_mutation_events_into_acceptance( + tmp_path: Path, command: str +) -> None: + root, task, brief, handoff, _ = _counted_validation(tmp_path) + handoff_path = root / ".work-bundle/orchestration/handoff/executor/active/handoff-task-004.yaml" + handoff_path.write_text( + "\n".join(execution_context._dump_yaml(handoff)) + "\n", encoding="utf-8" + ) + runtime = json.dumps( + [{"actor_kind": "controller", "paths": [brief["files"]["write"][0]]}] + ) + if command == "validate-executor-result": + operation = [command, "--task", str(task), "--handoff", str(handoff_path)] + else: + operation = [ + command, + "--id", + "task-004", + "--kind", + "task", + "--plan-id", + "plan-001", + "--status", + "Completed", + "--handoff", + str(handoff_path), + ] + from dispatcher import build_parser + + parsed = build_parser().parse_args( + [*operation, "--project-root", str(root), "--mutation-events", runtime] + ) + with pytest.raises(SystemExit, match="controller mutated task-owned implementation scope"): + parsed.func(parsed) + task_data, _ = execution_context._read_structured(task) + assert task_data.get("status") != "Completed" + + +@pytest.mark.parametrize("command", ["validate-executor-result", "set-plan-status"]) +def test_acceptance_cli_projects_all_controller_owned_runtime_inputs(command: str) -> None: + from dispatcher import build_parser + + expected = { + "mutation_events": [{"actor_kind": "controller", "paths": ["src/a.py"]}], + "accepted_dependency_deltas": [{"task_id": "task-a"}], + "prior_ownership": {"task-b": {"agent_id": "agent-a"}}, + "repair_continuity": {"task-b": {"binding_id": "binding-b"}}, + "authorized_replacements": ["task-b"], + } + if command == "validate-executor-result": + operation = [command, "--task", "task.md", "--handoff", "handoff.yaml"] + else: + operation = [command, "--id", "task-b", "--status", "Completed"] + argv = list(operation) + for name, value in expected.items(): + argv.extend(["--" + name.replace("_", "-"), json.dumps(value)]) + + parsed = build_parser().parse_args(argv) + + projected = execution_context._observation_kwargs(parsed) + assert {name: projected[name] for name in expected} == expected + + def test_source_records_keep_letter_suffixed_ids_distinct(tmp_path: Path) -> None: specification = tmp_path / "spec.md" body = ( @@ -2473,22 +2537,22 @@ def test_rf_task_repair_completion_rejects_stale_repair_frontier(tmp_path: Path) "staleness": {"is_stale": False, "reason": None, "supersedes": None}, } handoff["repository"] = [{"root": str(tmp_path), "target_kind": "git-backed", "preflight_kind": "git-clean-worktree", "baseline": "initial", "status": "clean"}] - assert execution_context.validate_executor_result_for_task(handoff, brief)["result_state"] == "completed" + execution_context._assert_handoff_review_matches_task(handoff, brief, "completed") stale = deepcopy(handoff) stale["acceptance_review"]["target_identity"] = {**new, "sha256": "3" * 64} with pytest.raises(SystemExit, match="repaired identity|frontier"): - execution_context.validate_executor_result_for_task(stale, brief) + execution_context._assert_handoff_review_matches_task(stale, brief, "completed") for field, value in (("required", False), ("reviewer_independent", False), ("review_target_kind", "wrong-kind")): invalid_prior = deepcopy(handoff) invalid_prior["acceptance_review"]["previous_review"][field] = value with pytest.raises(SystemExit, match="previous|task acceptance|review_target_kind"): - execution_context.validate_executor_result_for_task(invalid_prior, brief) + execution_context._assert_handoff_review_matches_task(invalid_prior, brief, "completed") fabricated = deepcopy(handoff) fabricated_identity = {**new, "sha256": "3" * 64, "source_tree": "f" * 40} fabricated["acceptance_review"]["target_identity"] = fabricated_identity fabricated["acceptance_review"]["repair_frontier"]["repaired_identity"] = fabricated_identity with pytest.raises(SystemExit, match="head|tree|Git identity"): - execution_context.validate_executor_result_for_task(fabricated, brief) + execution_context._assert_handoff_review_matches_task(fabricated, brief, "completed") def test_no_review_update_stays_closure_eligible_when_handoff_self_upgrades() -> None: diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 24ef5e2..df3396a 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -348,6 +348,85 @@ def test_completed_task_acceptance_rejects_controller_task_scope_mutation( execution_context.validate_executor_result_for_task(durable_history, brief) +def test_repair_acceptance_requires_exact_runtime_owner_and_continuity( + tmp_path: Path, +) -> None: + root, _, task = workspace(tmp_path) + scoped = _ensure_source_file(root) + git(root, "add", ".") + git(root, "commit", "-qm", "baseline") + brief = _without_terminal_evidence( + _document(root, task)["task_brief"], "Repair ownership fixture." + ) + binding = _bind_task_execution(root, brief) + scoped.write_text("def compile_task():\n return 'repair'\n", encoding="utf-8") + frontier = { + "prior_review_id": "review-prior", + "frozen_evidence_reference": execution_context.semantic_digest("evidence"), + } + handoff = { + "type": "executor-result", + "related": {"plan": brief["plan_id"], "task": brief["task_id"]}, + "result": {"state": "completed"}, + "task_fit_check": {"task": brief["task_id"], "result": "repaired"}, + "delegation_evidence": _delegation_evidence(), + "acceptance_review": {"required": False, "repair_frontier": frontier}, + "knowledge_disposition": { + "action": "none", + "reason": "No stable authority changed.", + "affected_authority": [], + }, + } + continuity = { + brief["task_id"]: { + "binding_id": binding["ownership"]["binding_id"], + "baseline_identity": execution_context.semantic_digest(binding["baseline"]), + "evidence_identity": frontier["frozen_evidence_reference"], + "previous_review_identity": frontier["prior_review_id"], + } + } + prior = {brief["task_id"]: _delegation_evidence()} + + accepted = execution_context.validate_executor_result_for_task( + handoff, + brief, + prior_ownership=prior, + repair_continuity=continuity, + ) + assert accepted["task_ownership"]["agent_id"] == "ctx-fixture-agent" + + replacement = deepcopy(handoff) + replacement["delegation_evidence"] = { + **_delegation_evidence(), + "agent_id": "unrelated-agent", + "run_id": "unrelated-run", + } + with pytest.raises(SystemExit, match="replacement is not authorized"): + execution_context.validate_executor_result_for_task( + replacement, + brief, + prior_ownership=prior, + repair_continuity=continuity, + ) + execution_context.validate_executor_result_for_task( + replacement, + brief, + prior_ownership=prior, + repair_continuity=continuity, + authorized_replacements={brief["task_id"]}, + ) + + stale = deepcopy(continuity) + stale[brief["task_id"]]["baseline_identity"] = "stale" + with pytest.raises(SystemExit, match="continuity identities do not match"): + execution_context.validate_executor_result_for_task( + handoff, + brief, + prior_ownership=prior, + repair_continuity=stale, + ) + + def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( tmp_path: Path, ) -> None: From 5d701f3481542ecefc7aa8a3910edd3b821a838f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 05:58:19 +0800 Subject: [PATCH 16/36] test(orchestration): isolate dispatcher module loading --- tests/test_orchestration_execution_context.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index bef8ef1..d4756c0 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -2,6 +2,7 @@ import argparse import hashlib +import importlib.util import json import re import shlex @@ -23,6 +24,20 @@ from execution_context import build_review_package, build_task_brief # noqa: E402 +def _load_orchestration_dispatcher(): + path = ORCHESTRATION / "dispatcher.py" + spec = importlib.util.spec_from_file_location( + "test_orchestration_execution_context_dispatcher", path + ) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +orchestration_dispatcher = _load_orchestration_dispatcher() + + def _counted_validation(tmp_path: Path, reuse_seconds: int = 3600, suffix: str = ""): root, _, task = workspace(tmp_path) _ensure_source_file(root) @@ -272,9 +287,7 @@ def test_normal_cli_projects_controller_mutation_events_into_acceptance( "--handoff", str(handoff_path), ] - from dispatcher import build_parser - - parsed = build_parser().parse_args( + parsed = orchestration_dispatcher.build_parser().parse_args( [*operation, "--project-root", str(root), "--mutation-events", runtime] ) with pytest.raises(SystemExit, match="controller mutated task-owned implementation scope"): @@ -285,8 +298,6 @@ def test_normal_cli_projects_controller_mutation_events_into_acceptance( @pytest.mark.parametrize("command", ["validate-executor-result", "set-plan-status"]) def test_acceptance_cli_projects_all_controller_owned_runtime_inputs(command: str) -> None: - from dispatcher import build_parser - expected = { "mutation_events": [{"actor_kind": "controller", "paths": ["src/a.py"]}], "accepted_dependency_deltas": [{"task_id": "task-a"}], @@ -302,7 +313,7 @@ def test_acceptance_cli_projects_all_controller_owned_runtime_inputs(command: st for name, value in expected.items(): argv.extend(["--" + name.replace("_", "-"), json.dumps(value)]) - parsed = build_parser().parse_args(argv) + parsed = orchestration_dispatcher.build_parser().parse_args(argv) projected = execution_context._observation_kwargs(parsed) assert {name: projected[name] for name in expected} == expected From 084e3398b43a18770d76c2fa4dd86d391966a75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 06:32:50 +0800 Subject: [PATCH 17/36] fix(orchestration): compose accepted repair chains --- scripts/orchestration/execution_context.py | 29 ++++- tests/test_wor108_context_projection.py | 118 +++++++++++++++++++++ 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 33b9ce7..d5ae031 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1510,6 +1510,9 @@ def _accepted_dependency_paths( control_root = Path(str(workspace.get("root") or "")).resolve() handoff_root = control_root / ".work-bundle/orchestration/handoff" admitted: set[str] = set() + chain_tail_by_dependency: dict[str, dict[str, Any]] = {} + last_checkpoint_by_path: dict[str, tuple[str, str]] = {} + current_head = _resolve_commit(execution_root, "HEAD") required_fields = { "task_id", "handoff_id", "handoff_sha256", "integrated_base", "integrated_head" } @@ -1550,7 +1553,6 @@ def _accepted_dependency_paths( source_head = str(repaired.get("revision") or "") integrated_base = _resolve_commit(execution_root, str(descriptor["integrated_base"])) integrated_head = _resolve_commit(execution_root, str(descriptor["integrated_head"])) - current_head = _resolve_commit(execution_root, "HEAD") if not source_base or not source_head: raise SystemExit(f"accepted dependency repair identity is incomplete: {handoff_id}") for commit, identity in ((source_base, previous), (source_head, repaired)): @@ -1558,6 +1560,18 @@ def _accepted_dependency_paths( tree = _git(execution_root, "rev-parse", f"{resolved}^{{tree}}").strip() if tree != identity.get("source_tree"): raise SystemExit(f"accepted dependency Git identity is mismatched: {handoff_id}") + prior_link = chain_tail_by_dependency.get(dependency_id) + if prior_link is not None: + if previous != prior_link["repaired_identity"]: + raise SystemExit( + "accepted dependency source chain is not ordered and contiguous: " + f"{handoff_id}" + ) + if integrated_base != prior_link["integrated_head"]: + raise SystemExit( + "accepted dependency integrated chain is non-adjacent: " + f"{handoff_id}" + ) if subprocess.run( ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", integrated_head, current_head], capture_output=True, @@ -1575,11 +1589,16 @@ def _accepted_dependency_paths( for line in _git(execution_root, "diff", "--name-status", source_base, source_head, "--").splitlines() for path in _paths_from_name_status(line) } - if paths and _git( - execution_root, "diff", "--name-only", integrated_head, "--", *sorted(paths) - ).strip(): + chain_tail_by_dependency[dependency_id] = { + "repaired_identity": repaired, + "integrated_head": integrated_head, + } + for path in paths: + last_checkpoint_by_path[path] = (integrated_head, handoff_id) + for path, (integrated_head, handoff_id) in last_checkpoint_by_path.items(): + if _git(execution_root, "diff", "--name-only", integrated_head, "--", path).strip(): raise SystemExit(f"accepted dependency path changed after integration: {handoff_id}") - admitted.update(paths) + admitted.add(path) return admitted diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index df3396a..f73fffc 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -558,6 +558,124 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( ) +def test_accepted_dependency_repair_chain_is_ordered_contiguous_and_last_wins( + tmp_path: Path, +) -> None: + root, _, _ = workspace(tmp_path) + dependency_path = "references/assets/orchestration/workflow.md" + dependency = root / dependency_path + dependency.parent.mkdir(parents=True, exist_ok=True) + dependency.write_text("version zero\n", encoding="utf-8") + git(root, "add", ".") + git(root, "commit", "-qm", "chain baseline") + commits = [git(root, "rev-parse", "HEAD")] + trees = [git(root, "rev-parse", "HEAD^{tree}")] + for number in range(1, 4): + dependency.write_text(f"version {number}\n", encoding="utf-8") + git(root, "add", dependency_path) + git(root, "commit", "-qm", f"accepted dependency repair {number}") + commits.append(git(root, "rev-parse", "HEAD")) + trees.append(git(root, "rev-parse", "HEAD^{tree}")) + unrelated = root / "tests/task-local.txt" + unrelated.parent.mkdir(parents=True, exist_ok=True) + unrelated.write_text("dependent task change\n", encoding="utf-8") + git(root, "add", str(unrelated.relative_to(root))) + git(root, "commit", "-qm", "dependent task change") + + task = { + "task_id": "dependent-task", + "plan_id": "plan-001", + "depends_on": ["task-dependency"], + "workspace": {"root": str(root)}, + } + handoff_root = root / ".work-bundle/orchestration/handoff/executor/active" + handoff_root.mkdir(parents=True, exist_ok=True) + + def identity(index: int) -> dict[str, object]: + return { + "artifact_id": "task-dependency", + "revision": commits[index], + "sha256": execution_context.semantic_digest( + {"commit": commits[index], "tree": trees[index]} + ), + "source_tree": trees[index], + } + + handoffs: list[dict[str, object]] = [] + descriptors: list[dict[str, object]] = [] + paths: list[Path] = [] + for index in range(1, 4): + handoff_id = f"handoff-chain-{index}" + handoff = { + "id": handoff_id, + "type": "executor-result", + "related": {"plan": "plan-001", "task": "task-dependency"}, + "result": {"state": "completed"}, + "acceptance_review": { + "required": True, + "verdict": "accept", + "review_mode": "repair", + "target_identity": identity(index), + "repair_frontier": { + "previous_reviewed_identity": identity(index - 1), + "repaired_identity": identity(index), + }, + }, + } + path = handoff_root / f"{handoff_id}.yaml" + path.write_text("\n".join(execution_context._dump_yaml(handoff)) + "\n", encoding="utf-8") + handoffs.append(handoff) + paths.append(path) + descriptors.append( + { + "task_id": "task-dependency", + "handoff_id": handoff_id, + "handoff_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + "integrated_base": commits[index - 1], + "integrated_head": commits[index], + } + ) + + assert execution_context._accepted_dependency_paths(task, root, descriptors) == { + dependency_path + } + + with pytest.raises(SystemExit, match="ordered|source.*contiguous"): + execution_context._accepted_dependency_paths( + task, root, [descriptors[1], descriptors[0], descriptors[2]] + ) + with pytest.raises(SystemExit, match="source.*contiguous|incomplete"): + execution_context._accepted_dependency_paths(task, root, [descriptors[0], descriptors[2]]) + nonadjacent = deepcopy(descriptors) + nonadjacent[1] = {**nonadjacent[1], "integrated_base": commits[0]} + with pytest.raises(SystemExit, match="integrated.*contiguous|non-adjacent"): + execution_context._accepted_dependency_paths(task, root, nonadjacent) + missing = deepcopy(descriptors) + missing[1] = {**missing[1], "handoff_id": "handoff-chain-missing"} + with pytest.raises(SystemExit, match="missing or ambiguous"): + execution_context._accepted_dependency_paths(task, root, missing) + + rejected = deepcopy(handoffs[1]) + rejected["acceptance_review"]["verdict"] = "pending" + paths[1].write_text( + "\n".join(execution_context._dump_yaml(rejected)) + "\n", encoding="utf-8" + ) + unaccepted = deepcopy(descriptors) + unaccepted[1] = { + **unaccepted[1], + "handoff_sha256": hashlib.sha256(paths[1].read_bytes()).hexdigest(), + } + with pytest.raises(SystemExit, match="not an accepted repair"): + execution_context._accepted_dependency_paths(task, root, unaccepted) + paths[1].write_text( + "\n".join(execution_context._dump_yaml(handoffs[1])) + "\n", encoding="utf-8" + ) + + dependency.write_text("unaccepted later mutation\n", encoding="utf-8") + with pytest.raises(SystemExit, match="changed after integration"): + execution_context._accepted_dependency_paths(task, root, descriptors) + + def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( tmp_path: Path, ) -> None: From 37bd559d958dc3ac0385a00490ebf5c9739a4f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 06:54:27 +0800 Subject: [PATCH 18/36] fix(orchestration): anchor accepted repair chains --- scripts/orchestration/dispatcher.py | 1 + scripts/orchestration/execution_context.py | 44 +++++++++++++ tests/test_orchestration_execution_context.py | 18 +++++- tests/test_wor108_context_projection.py | 62 ++++++++++++++++++- 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 1fc5efd..94a3c8b 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -73,6 +73,7 @@ def build_parser() -> argparse.ArgumentParser: review_package.add_argument("--handoff", required=True) review_package.add_argument("--base", required=True) review_package.add_argument("--head", required=True) + _add_acceptance_runtime_inputs(review_package) review_package.set_defaults(func=cmd_build_review_package) validate_result = sub.add_parser("validate-executor-result", parents=[parent]) validate_result.add_argument("--task", required=True) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index d5ae031..8c3460d 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1511,6 +1511,7 @@ def _accepted_dependency_paths( handoff_root = control_root / ".work-bundle/orchestration/handoff" admitted: set[str] = set() chain_tail_by_dependency: dict[str, dict[str, Any]] = {} + chain_by_dependency: dict[str, dict[str, Any]] = {} last_checkpoint_by_path: dict[str, tuple[str, str]] = {} current_head = _resolve_commit(execution_root, "HEAD") required_fields = { @@ -1593,8 +1594,51 @@ def _accepted_dependency_paths( "repaired_identity": repaired, "integrated_head": integrated_head, } + chain = chain_by_dependency.setdefault( + dependency_id, + { + "first_previous_identity": previous, + "last_repaired_identity": repaired, + "accepted_edges": set(), + }, + ) + chain["last_repaired_identity"] = repaired + chain["accepted_edges"].add( + semantic_digest({"previous": previous, "repaired": repaired}) + ) for path in paths: last_checkpoint_by_path[path] = (integrated_head, handoff_id) + for dependency_id, chain in chain_by_dependency.items(): + for path in sorted(handoff_root.glob("executor/*/*")): + if not path.is_file() or path.is_symlink(): + continue + try: + candidate, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + related = candidate.get("related") if isinstance(candidate.get("related"), dict) else {} + result = candidate.get("result") if isinstance(candidate.get("result"), dict) else {} + review = candidate.get("acceptance_review") if isinstance(candidate.get("acceptance_review"), dict) else {} + frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), dict) else {} + previous = frontier.get("previous_reviewed_identity") if isinstance(frontier.get("previous_reviewed_identity"), dict) else {} + repaired = frontier.get("repaired_identity") if isinstance(frontier.get("repaired_identity"), dict) else {} + if (related.get("task") != dependency_id or result.get("state") != "completed" + or review.get("verdict") != "accept" or review.get("review_mode") != "repair" + or repaired != review.get("target_identity") or not previous or not repaired): + continue + edge = semantic_digest({"previous": previous, "repaired": repaired}) + if edge in chain["accepted_edges"]: + continue + if repaired == chain["first_previous_identity"]: + raise SystemExit( + "accepted dependency repair chain is not anchored at its known accepted start: " + f"{dependency_id}" + ) + if previous == chain["last_repaired_identity"]: + raise SystemExit( + "accepted dependency repair chain is incomplete at its known accepted head: " + f"{dependency_id}" + ) for path, (integrated_head, handoff_id) in last_checkpoint_by_path.items(): if _git(execution_root, "diff", "--name-only", integrated_head, "--", path).strip(): raise SystemExit(f"accepted dependency path changed after integration: {handoff_id}") diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index d4756c0..aad29b3 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -296,7 +296,9 @@ def test_normal_cli_projects_controller_mutation_events_into_acceptance( assert task_data.get("status") != "Completed" -@pytest.mark.parametrize("command", ["validate-executor-result", "set-plan-status"]) +@pytest.mark.parametrize( + "command", ["validate-executor-result", "set-plan-status", "build-review-package"] +) def test_acceptance_cli_projects_all_controller_owned_runtime_inputs(command: str) -> None: expected = { "mutation_events": [{"actor_kind": "controller", "paths": ["src/a.py"]}], @@ -307,8 +309,20 @@ def test_acceptance_cli_projects_all_controller_owned_runtime_inputs(command: st } if command == "validate-executor-result": operation = [command, "--task", "task.md", "--handoff", "handoff.yaml"] - else: + elif command == "set-plan-status": operation = [command, "--id", "task-b", "--status", "Completed"] + else: + operation = [ + command, + "--task", + "task.md", + "--handoff", + "handoff.yaml", + "--base", + "base", + "--head", + "head", + ] argv = list(operation) for name, value in expected.items(): argv.extend(["--" + name.replace("_", "-"), json.dumps(value)]) diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index f73fffc..85afdd9 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -3,6 +3,7 @@ import builtins import hashlib import json +import subprocess import sys from copy import deepcopy from pathlib import Path @@ -431,6 +432,13 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( tmp_path: Path, ) -> None: root, _, task = workspace(tmp_path) + task.write_text( + task.read_text(encoding="utf-8").replace( + "phase_id: phase-001\n", + "phase_id: phase-001\ndepends_on: [task-dependency]\n", + ), + encoding="utf-8", + ) scoped = _ensure_source_file(root) dependency = root / "references/assets/orchestration/workflow.md" dependency.parent.mkdir(parents=True, exist_ok=True) @@ -441,7 +449,6 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( baseline_tree = git(root, "rev-parse", "HEAD^{tree}") brief = _document(root, task)["task_brief"] - brief["depends_on"] = ["task-dependency"] _bind_task_execution(root, brief) dependency.write_text("accepted dependency\n", encoding="utf-8") @@ -525,6 +532,54 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( ) assert accepted["result_state"] == "completed" + cli_handoff = deepcopy(handoff) + cli_handoff["result"]["state"] = "partial" + cli_handoff["changes"] = { + "files": [ + { + "path": "scripts/orchestration/execution_context.py", + "action": "modified", + } + ] + } + cli_handoff["codegraph"] = [ + { + "root": str(root.resolve()), + "applicable": False, + "up_to_date": False, + "reason": "no-index", + } + ] + cli_handoff_path = ( + root / ".work-bundle/orchestration/handoff/executor/active/handoff-task-004.yaml" + ) + cli_handoff_path.write_text( + "\n".join(execution_context._dump_yaml(cli_handoff)) + "\n", encoding="utf-8" + ) + cli = subprocess.run( + [ + sys.executable, + str(REPO_ROOT / "scripts/orch.py"), + "build-review-package", + "--project-root", + str(root), + "--task", + str(task), + "--handoff", + str(cli_handoff_path), + "--base", + baseline, + "--head", + git(root, "rev-parse", "HEAD"), + "--accepted-dependency-deltas", + json.dumps([descriptor]), + ], + capture_output=True, + text=True, + ) + assert cli.returncode == 0, cli.stderr + assert "review-package.md" in cli.stdout + stale = {**descriptor, "handoff_sha256": "0" * 64} with pytest.raises(SystemExit, match="handoff identity is stale"): execution_context.validate_executor_result_for_task( @@ -640,6 +695,11 @@ def identity(index: int) -> dict[str, object]: dependency_path } + with pytest.raises(SystemExit, match="anchored|incomplete"): + execution_context._accepted_dependency_paths(task, root, descriptors[1:]) + with pytest.raises(SystemExit, match="anchored|incomplete"): + execution_context._accepted_dependency_paths(task, root, descriptors[-1:]) + with pytest.raises(SystemExit, match="ordered|source.*contiguous"): execution_context._accepted_dependency_paths( task, root, [descriptors[1], descriptors[0], descriptors[2]] From 3529b175ffde3184efd632f41c9c16ac93e5e9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 07:03:50 +0800 Subject: [PATCH 19/36] fix(orchestration): bind dependency repairs to plan --- scripts/orchestration/execution_context.py | 12 +++++++++--- tests/test_wor108_context_projection.py | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 8c3460d..d311500 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1506,6 +1506,7 @@ def _accepted_dependency_paths( if not descriptors: return set() dependencies = {str(value) for value in _as_list(task.get("depends_on"))} + plan_id = str(task.get("plan_id") or "") workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} control_root = Path(str(workspace.get("root") or "")).resolve() handoff_root = control_root / ".work-bundle/orchestration/handoff" @@ -1546,8 +1547,12 @@ def _accepted_dependency_paths( frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), dict) else {} previous = frontier.get("previous_reviewed_identity") if isinstance(frontier.get("previous_reviewed_identity"), dict) else {} repaired = frontier.get("repaired_identity") if isinstance(frontier.get("repaired_identity"), dict) else {} - if (related.get("task") != dependency_id or result.get("state") != "completed" - or review.get("verdict") != "accept" or review.get("review_mode") != "repair" + if related.get("plan") != plan_id or related.get("task") != dependency_id: + raise SystemExit( + f"accepted dependency handoff plan/task identity is mismatched: {handoff_id}" + ) + if (result.get("state") != "completed" or review.get("verdict") != "accept" + or review.get("review_mode") != "repair" or repaired != review.get("target_identity")): raise SystemExit(f"dependency handoff is not an accepted repair result: {handoff_id}") source_base = str(previous.get("revision") or "") @@ -1622,7 +1627,8 @@ def _accepted_dependency_paths( frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), dict) else {} previous = frontier.get("previous_reviewed_identity") if isinstance(frontier.get("previous_reviewed_identity"), dict) else {} repaired = frontier.get("repaired_identity") if isinstance(frontier.get("repaired_identity"), dict) else {} - if (related.get("task") != dependency_id or result.get("state") != "completed" + if (related.get("plan") != plan_id or related.get("task") != dependency_id + or result.get("state") != "completed" or review.get("verdict") != "accept" or review.get("review_mode") != "repair" or repaired != review.get("target_identity") or not previous or not repaired): continue diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 85afdd9..3e30c80 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -580,6 +580,23 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( assert cli.returncode == 0, cli.stderr assert "review-package.md" in cli.stdout + accepted_bytes = accepted_path.read_bytes() + wrong_plan_handoff = deepcopy(dependency_handoff) + wrong_plan_handoff["related"]["plan"] = "plan-WRONG" + accepted_path.write_text( + "\n".join(execution_context._dump_yaml(wrong_plan_handoff)) + "\n", + encoding="utf-8", + ) + wrong_plan = { + **descriptor, + "handoff_sha256": hashlib.sha256(accepted_path.read_bytes()).hexdigest(), + } + with pytest.raises(SystemExit, match="plan"): + execution_context.validate_executor_result_for_task( + handoff, current, observe=True, accepted_dependency_deltas=[wrong_plan] + ) + accepted_path.write_bytes(accepted_bytes) + stale = {**descriptor, "handoff_sha256": "0" * 64} with pytest.raises(SystemExit, match="handoff identity is stale"): execution_context.validate_executor_result_for_task( @@ -590,7 +607,6 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( execution_context.validate_executor_result_for_task( handoff, current, observe=True, accepted_dependency_deltas=[mismatched] ) - accepted_bytes = accepted_path.read_bytes() unaccepted_handoff = deepcopy(dependency_handoff) unaccepted_handoff["acceptance_review"]["verdict"] = "pending" accepted_path.write_text( From 9f46c81bd6b2a0f794d5c744cefd196b51b1d95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 07:46:00 +0800 Subject: [PATCH 20/36] fix(orchestration): validate cumulative accepted result deltas --- scripts/orchestration/execution_context.py | 234 +++++++++++++++++++++ tests/test_wor108_context_projection.py | 221 +++++++++++++++++++ 2 files changed, 455 insertions(+) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index d311500..a6972cf 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1497,6 +1497,233 @@ def _assert_task_caused_delta_in_write_scope( raise SystemExit(f"Unauthorized task-caused delta outside write scope: {relative}") +def _exact_handoff_reference( + handoff_root: Path, + reference: object, +) -> tuple[Path, dict[str, Any]]: + if not isinstance(reference, Mapping) or set(reference) != {"handoff_id", "handoff_sha256"}: + raise SystemExit("accepted dependency handoff reference must use the closed identity shape") + handoff_id = str(reference["handoff_id"]) + matches: list[tuple[Path, dict[str, Any]]] = [] + for path in sorted(handoff_root.glob("executor/*/*")): + if not path.is_file() or path.is_symlink(): + continue + try: + document, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + if document.get("id") == handoff_id: + matches.append((path, document)) + if len(matches) != 1: + raise SystemExit(f"accepted dependency handoff identity is missing or ambiguous: {handoff_id}") + path, handoff = matches[0] + if hashlib.sha256(path.read_bytes()).hexdigest() != str(reference["handoff_sha256"]): + raise SystemExit(f"accepted dependency handoff identity is stale: {handoff_id}") + return path, handoff + + +def _review_without_history(review: Mapping[str, Any]) -> dict[str, Any]: + return {key: value for key, value in review.items() if key != "previous_review"} + + +def _cumulative_accepted_dependency_paths( + task: dict[str, Any], + execution_root: Path, + descriptors: list[Mapping[str, object]], +) -> set[str]: + required_fields = { + "task_id", "accepted_result_base", "review_chain", "integrated_base", "integrated_head" + } + dependencies = {str(value) for value in _as_list(task.get("depends_on"))} + plan_id = str(task.get("plan_id") or "") + workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} + handoff_root = ( + Path(str(workspace.get("root") or "")).resolve() + / ".work-bundle/orchestration/handoff" + ) + current_head = _resolve_commit(execution_root, "HEAD") + admitted: set[str] = set() + seen_dependencies: set[str] = set() + for descriptor in descriptors: + if set(descriptor) != required_fields: + raise SystemExit( + "accepted_dependency_deltas cumulative entries must use the closed runtime identity shape" + ) + dependency_id = str(descriptor["task_id"]) + if dependency_id not in dependencies: + raise SystemExit(f"accepted_dependency_deltas names undeclared dependency: {dependency_id}") + if dependency_id in seen_dependencies: + raise SystemExit(f"accepted_dependency_deltas duplicates dependency: {dependency_id}") + seen_dependencies.add(dependency_id) + chain_references = descriptor["review_chain"] + if (not isinstance(chain_references, list) or not chain_references + or any(not isinstance(item, Mapping) for item in chain_references)): + raise SystemExit("accepted dependency review_chain must be non-empty and exact") + reference_ids = [str(item.get("handoff_id") or "") for item in chain_references] + if len(reference_ids) != len(set(reference_ids)): + raise SystemExit("accepted dependency review_chain cannot contain duplicated handoffs") + + _, base_handoff = _exact_handoff_reference( + handoff_root, descriptor["accepted_result_base"] + ) + base_related = base_handoff.get("related") if isinstance(base_handoff.get("related"), dict) else {} + base_result = base_handoff.get("result") if isinstance(base_handoff.get("result"), dict) else {} + base_review = base_handoff.get("acceptance_review") if isinstance(base_handoff.get("acceptance_review"), dict) else {} + if (base_related.get("plan") != plan_id or base_related.get("task") != dependency_id + or base_result.get("state") != "completed" or base_review.get("verdict") != "accept"): + raise SystemExit("accepted dependency result base is not an accepted plan/task result") + + chain: list[tuple[dict[str, Any], dict[str, Any], dict[str, Any]]] = [] + for reference in chain_references: + _, handoff = _exact_handoff_reference(handoff_root, reference) + related = handoff.get("related") if isinstance(handoff.get("related"), dict) else {} + result = handoff.get("result") if isinstance(handoff.get("result"), dict) else {} + review = handoff.get("acceptance_review") if isinstance(handoff.get("acceptance_review"), dict) else {} + if related.get("plan") != plan_id or related.get("task") != dependency_id: + raise SystemExit("accepted dependency review_chain plan/task identity is mismatched") + chain.append((handoff, result, review)) + + try: + from review_runtime import ( + ReviewContractError, + review_evidence_identity, + validate_task_review_record, + ) + validate_task_review_record(base_review) + base_identity = base_review.get("target_identity") + if (not isinstance(base_identity, Mapping) + or base_identity.get("artifact_id") != dependency_id + or base_review.get("reviewed_head") != base_identity.get("revision")): + raise SystemExit("accepted dependency result base identity is mismatched") + previous_review = base_review + seen_review_ids = {str(base_review.get("review_id") or "")} + for _, result, review in chain: + validate_task_review_record(review) + review_id = str(review.get("review_id") or "") + identity = review.get("target_identity") + if (review_id in seen_review_ids or not isinstance(identity, Mapping) + or identity.get("artifact_id") != dependency_id + or review.get("reviewed_head") != identity.get("revision")): + raise SystemExit("accepted dependency review_chain review identity is duplicated or mismatched") + seen_review_ids.add(review_id) + revision = _resolve_commit(execution_root, str(identity.get("revision") or "")) + tree = _git(execution_root, "rev-parse", f"{revision}^{{tree}}").strip() + if tree != identity.get("source_tree"): + raise SystemExit("accepted dependency review_chain Git identity is mismatched") + carried = review.get("previous_review") + if (not isinstance(carried, Mapping) or "previous_review" in carried + or _review_without_history(carried) != _review_without_history(previous_review)): + raise SystemExit("accepted dependency review_chain prior review is missing or mismatched") + mode = review.get("review_mode") + if mode == "repair": + frontier = review.get("repair_frontier") + if not isinstance(frontier, Mapping): + raise SystemExit("accepted dependency review_chain repair frontier is missing") + blocking = [ + str(item.get("finding_id")) + for item in _as_list(previous_review.get("findings")) + if isinstance(item, Mapping) and item.get("severity") == "blocking" + ] + if (frontier.get("prior_review_id") != previous_review.get("review_id") + or frontier.get("previous_reviewed_identity") != previous_review.get("target_identity") + or frontier.get("repaired_identity") != review.get("target_identity") + or list(frontier.get("blocking_finding_ids") or []) != blocking + or frontier.get("frozen_evidence_reference") != review_evidence_identity(previous_review)): + raise SystemExit("accepted dependency review_chain repair continuity is mismatched") + else: + reset = review.get("review_reset") + if not isinstance(reset, Mapping) or reset.get("prior_review_id") != previous_review.get("review_id"): + raise SystemExit("accepted dependency review_chain initial reset is non-contiguous") + if review.get("verdict") not in {"repair", "accept"}: + raise SystemExit("accepted dependency review_chain contains a blocked review") + if review.get("verdict") == "accept" and result.get("state") != "completed": + raise SystemExit("accepted dependency review_chain accepted result is incomplete") + previous_review = review + except ReviewContractError as error: + raise SystemExit(f"accepted dependency review_chain is invalid: {error}") from error + + _, final_result, final_review = chain[-1] + if final_review.get("verdict") != "accept" or final_result.get("state") != "completed": + raise SystemExit("accepted dependency review_chain final result is not accepted") + base_identity = base_review.get("target_identity") + final_identity = final_review.get("target_identity") + if not isinstance(base_identity, Mapping) or not isinstance(final_identity, Mapping): + raise SystemExit("accepted dependency cumulative result identity is incomplete") + for identity in (base_identity, final_identity): + revision = _resolve_commit(execution_root, str(identity.get("revision") or "")) + tree = _git(execution_root, "rev-parse", f"{revision}^{{tree}}").strip() + if tree != identity.get("source_tree"): + raise SystemExit("accepted dependency cumulative Git identity is mismatched") + + final_review_id = str(final_review.get("review_id") or "") + chain_ids = set(reference_ids) + successor_edges: list[tuple[str, str, bool]] = [] + for path in sorted(handoff_root.glob("executor/*/*")): + if not path.is_file() or path.is_symlink(): + continue + try: + candidate, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + if str(candidate.get("id") or "") in chain_ids: + continue + related = candidate.get("related") if isinstance(candidate.get("related"), dict) else {} + result = candidate.get("result") if isinstance(candidate.get("result"), dict) else {} + review = candidate.get("acceptance_review") if isinstance(candidate.get("acceptance_review"), dict) else {} + frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), dict) else {} + reset = review.get("review_reset") if isinstance(review.get("review_reset"), dict) else {} + prior_id = frontier.get("prior_review_id") or reset.get("prior_review_id") + if (related.get("plan") == plan_id and related.get("task") == dependency_id + and isinstance(prior_id, str) and prior_id): + successor_edges.append(( + prior_id, + str(review.get("review_id") or ""), + result.get("state") == "completed" and review.get("verdict") == "accept", + )) + reachable = {final_review_id} + while True: + additions = {current for prior, current, _ in successor_edges if prior in reachable} + if additions.issubset(reachable): + break + reachable.update(additions) + if any(accepted and prior in reachable for prior, _, accepted in successor_edges): + raise SystemExit("accepted dependency review_chain terminal result is stale") + + source_base = _resolve_commit(execution_root, str(base_identity.get("revision") or "")) + source_head = _resolve_commit(execution_root, str(final_identity.get("revision") or "")) + if subprocess.run( + ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", source_base, source_head], + capture_output=True, + check=False, + ).returncode: + raise SystemExit("accepted dependency cumulative source chain is non-ancestral") + integrated_base = _resolve_commit(execution_root, str(descriptor["integrated_base"])) + integrated_head = _resolve_commit(execution_root, str(descriptor["integrated_head"])) + if subprocess.run( + ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", integrated_head, current_head], + capture_output=True, + check=False, + ).returncode: + raise SystemExit("accepted dependency cumulative integration checkpoint is not current") + source_diff = _git(execution_root, "diff", "--binary", source_base, source_head, "--") + integrated_diff = _git( + execution_root, "diff", "--binary", integrated_base, integrated_head, "--" + ) + if source_diff != integrated_diff: + raise SystemExit("accepted dependency cumulative integration checkpoint is mismatched") + paths = { + path + for line in _git(execution_root, "diff", "--name-status", source_base, source_head, "--").splitlines() + for path in _paths_from_name_status(line) + } + if paths and _git( + execution_root, "diff", "--name-only", integrated_head, "--", *sorted(paths) + ).strip(): + raise SystemExit("accepted dependency cumulative path changed after integration") + admitted.update(paths) + return admitted + + def _accepted_dependency_paths( task: dict[str, Any], execution_root: Path, @@ -1505,6 +1732,13 @@ def _accepted_dependency_paths( descriptors = list(accepted_dependency_deltas or []) if not descriptors: return set() + cumulative_fields = { + "task_id", "accepted_result_base", "review_chain", "integrated_base", "integrated_head" + } + if all(isinstance(item, Mapping) and set(item) == cumulative_fields for item in descriptors): + return _cumulative_accepted_dependency_paths(task, execution_root, descriptors) + if any(isinstance(item, Mapping) and set(item) == cumulative_fields for item in descriptors): + raise SystemExit("accepted_dependency_deltas cannot mix cumulative and legacy identities") dependencies = {str(value) for value in _as_list(task.get("depends_on"))} plan_id = str(task.get("plan_id") or "") workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 3e30c80..04b837e 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -19,6 +19,7 @@ sys.path.insert(0, str(path)) import execution_context # noqa: E402 +import review_runtime # noqa: E402 from stage_events import StageEventError, validate_stage_event # noqa: E402 from test_orchestration_execution_context import ( # noqa: E402 _bind_task_execution, @@ -752,6 +753,226 @@ def identity(index: int) -> dict[str, object]: execution_context._accepted_dependency_paths(task, root, descriptors) +def test_cumulative_accepted_result_delta_requires_complete_final_review_chain( + tmp_path: Path, +) -> None: + root, _, _ = workspace(tmp_path) + dependency_path = "references/assets/orchestration/workflow.md" + repair_path = "scripts/orchestration/dependency_repair.py" + final_path = "tests/dependency_result.md" + dependency = root / dependency_path + dependency.parent.mkdir(parents=True, exist_ok=True) + dependency.write_text("accepted base\n", encoding="utf-8") + git(root, "add", ".") + git(root, "commit", "-qm", "accepted base") + commits = [git(root, "rev-parse", "HEAD")] + trees = [git(root, "rev-parse", "HEAD^{tree}")] + for label, changed_path in ( + ("initial repair target", dependency_path), + ("accepted repair", repair_path), + ("fresh accepted result", final_path), + ): + changed = root / changed_path + changed.parent.mkdir(parents=True, exist_ok=True) + changed.write_text(label + "\n", encoding="utf-8") + git(root, "add", changed_path) + git(root, "commit", "-qm", label) + commits.append(git(root, "rev-parse", "HEAD")) + trees.append(git(root, "rev-parse", "HEAD^{tree}")) + local = root / "tests/task-local.txt" + local.parent.mkdir(parents=True, exist_ok=True) + local.write_text("dependent task\n", encoding="utf-8") + git(root, "add", str(local.relative_to(root))) + git(root, "commit", "-qm", "dependent task") + + def identity(index: int) -> dict[str, object]: + return { + "artifact_id": "task-dependency", + "revision": commits[index], + "sha256": execution_context.semantic_digest( + {"commit": commits[index], "tree": trees[index]} + ), + "source_tree": trees[index], + } + + def reviewer(agent: str) -> dict[str, object]: + return { + "agent_id": agent, + "capability": "judgment", + "authorship": "none", + "repair_participation": "none", + "decision_participation": "none", + "deliberation_participation": "none", + "context_origin": "direct_source", + } + + def review(review_id: str, index: int, verdict: str) -> dict[str, object]: + return { + "required": True, + "reviewer_independent": True, + "verdict": verdict, + "reviewed_head": commits[index], + "review_id": review_id, + "review_mode": "initial", + "review_target_kind": "task", + "repair_frontier": None, + "review_reset": None, + "target_identity": identity(index), + "reviewer": reviewer("reviewer-" + review_id), + "evidence": { + "mode": "direct", + "capabilities": ["source inspection"], + "unavailable_evidence": [], + "commands": [], + "artifacts": [], + }, + "findings": [], + "started_at": f"2026-09-06T00:0{index}:00Z", + "completed_at": f"2026-09-06T00:0{index}:30Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + + base_review = review("review-base", 0, "accept") + initial_repair = review("review-initial-repair", 1, "repair") + initial_repair["review_reset"] = { + "prior_review_id": "review-base", + "reason_class": "validation_allocation", + "reason": "Validation authority changed.", + } + initial_repair["previous_review"] = base_review + initial_repair["findings"] = [{ + "finding_id": "CHAIN-FINDING", + "stage": "implementation", + "class": "implementation_defect", + "severity": "blocking", + "first_broken_artifact": "implementation", + "obligation_basis": "accepted_requirement", + "evidence": [{ + "kind": "test", "locator": "CHAIN", "digest_or_identity": "red", + "observation": "repair required", + }], + "target_identity": identity(1), + "summary": "Repair the chain.", + "recommended_owner": "task_owner", + "disposition": "repair_task", + }] + accepted_repair = review("review-accepted-repair", 2, "accept") + accepted_repair["review_mode"] = "repair" + accepted_repair["repair_frontier"] = { + "prior_review_id": "review-initial-repair", + "blocking_finding_ids": ["CHAIN-FINDING"], + "previous_reviewed_identity": identity(1), + "repaired_identity": identity(2), + "affected_boundaries": [dependency_path], + "frozen_evidence_reference": review_runtime.review_evidence_identity(initial_repair), + } + accepted_repair["previous_review"] = { + key: value for key, value in initial_repair.items() if key != "previous_review" + } + final_review = review("review-final", 3, "accept") + final_review["review_reset"] = { + "prior_review_id": "review-accepted-repair", + "reason_class": "validation_allocation", + "reason": "Cumulative result validation changed.", + } + final_review["previous_review"] = { + key: value for key, value in accepted_repair.items() if key != "previous_review" + } + + handoff_root = root / ".work-bundle/orchestration/handoff/executor/active" + handoff_root.mkdir(parents=True, exist_ok=True) + records = [ + ("handoff-base", base_review, "completed"), + ("handoff-initial-repair", initial_repair, "partial"), + ("handoff-accepted-repair", accepted_repair, "completed"), + ("handoff-final", final_review, "completed"), + ] + references: dict[str, dict[str, str]] = {} + for handoff_id, acceptance, state in records: + path = handoff_root / f"{handoff_id}.yaml" + document = { + "id": handoff_id, + "type": "executor-result", + "related": {"plan": "plan-001", "task": "task-dependency"}, + "result": {"state": state}, + "acceptance_review": acceptance, + } + rendered = "\n".join(execution_context._dump_yaml(document)) + "\n" + path.write_text(rendered.replace(": none\n", ': "none"\n'), encoding="utf-8") + references[handoff_id] = { + "handoff_id": handoff_id, + "handoff_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + } + descriptor = { + "task_id": "task-dependency", + "accepted_result_base": references["handoff-base"], + "review_chain": [ + references["handoff-initial-repair"], + references["handoff-accepted-repair"], + references["handoff-final"], + ], + "integrated_base": commits[0], + "integrated_head": commits[3], + } + task = { + "task_id": "dependent-task", + "plan_id": "plan-001", + "depends_on": ["task-dependency"], + "workspace": {"root": str(root)}, + } + + assert execution_context._accepted_dependency_paths(task, root, [descriptor]) == { + dependency_path, repair_path, final_path + } + missing = deepcopy(descriptor) + missing["review_chain"] = missing["review_chain"][1:] + with pytest.raises(SystemExit, match="chain|prior"): + execution_context._accepted_dependency_paths(task, root, [missing]) + reordered = deepcopy(descriptor) + reordered["review_chain"][0], reordered["review_chain"][1] = ( + reordered["review_chain"][1], reordered["review_chain"][0] + ) + with pytest.raises(SystemExit, match="chain|prior|ordered"): + execution_context._accepted_dependency_paths(task, root, [reordered]) + intermediate = deepcopy(descriptor) + intermediate["review_chain"] = intermediate["review_chain"][:1] + intermediate["integrated_head"] = commits[1] + with pytest.raises(SystemExit, match="final|accept"): + execution_context._accepted_dependency_paths(task, root, [intermediate]) + wrong_checkpoint = deepcopy(descriptor) + wrong_checkpoint["integrated_head"] = commits[2] + with pytest.raises(SystemExit, match="checkpoint|mismatched"): + execution_context._accepted_dependency_paths(task, root, [wrong_checkpoint]) + stale_identity = deepcopy(descriptor) + stale_identity["review_chain"][-1]["handoff_sha256"] = "0" * 64 + with pytest.raises(SystemExit, match="stale"): + execution_context._accepted_dependency_paths(task, root, [stale_identity]) + + commits.append(git(root, "rev-parse", "HEAD")) + trees.append(git(root, "rev-parse", "HEAD^{tree}")) + later_review = review("review-later", 4, "accept") + later_review["review_reset"] = { + "prior_review_id": "review-final", + "reason_class": "validation_allocation", + "reason": "A later accepted result superseded the terminal result.", + } + later_review["previous_review"] = { + key: value for key, value in final_review.items() if key != "previous_review" + } + later_path = handoff_root / "handoff-later.yaml" + later_document = { + "id": "handoff-later", + "type": "executor-result", + "related": {"plan": "plan-001", "task": "task-dependency"}, + "result": {"state": "completed"}, + "acceptance_review": later_review, + } + rendered = "\n".join(execution_context._dump_yaml(later_document)) + "\n" + later_path.write_text(rendered.replace(": none\n", ': "none"\n'), encoding="utf-8") + with pytest.raises(SystemExit, match="terminal result is stale"): + execution_context._accepted_dependency_paths(task, root, [descriptor]) + + def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( tmp_path: Path, ) -> None: From 6203ff118a60295b1a590cb54d657858bed6623d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 08:01:41 +0800 Subject: [PATCH 21/36] fix(orchestration): enforce cumulative review succession --- scripts/orchestration/execution_context.py | 22 +++++++++---------- tests/test_wor108_context_projection.py | 25 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index a6972cf..6c387aa 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1587,9 +1587,9 @@ def _cumulative_accepted_dependency_paths( from review_runtime import ( ReviewContractError, review_evidence_identity, - validate_task_review_record, + validate_task_acceptance_review, ) - validate_task_review_record(base_review) + validate_task_acceptance_review(base_review) base_identity = base_review.get("target_identity") if (not isinstance(base_identity, Mapping) or base_identity.get("artifact_id") != dependency_id @@ -1598,7 +1598,10 @@ def _cumulative_accepted_dependency_paths( previous_review = base_review seen_review_ids = {str(base_review.get("review_id") or "")} for _, result, review in chain: - validate_task_review_record(review) + # The bounded previous_review embedded in each chain link lets the + # native sequence validator enforce repair continuity and material- + # change reset isolation without reacquiring older history. + validate_task_acceptance_review(review) review_id = str(review.get("review_id") or "") identity = review.get("target_identity") if (review_id in seen_review_ids or not isinstance(identity, Mapping) @@ -1657,7 +1660,7 @@ def _cumulative_accepted_dependency_paths( final_review_id = str(final_review.get("review_id") or "") chain_ids = set(reference_ids) - successor_edges: list[tuple[str, str, bool]] = [] + successor_edges: list[tuple[str, str]] = [] for path in sorted(handoff_root.glob("executor/*/*")): if not path.is_file() or path.is_symlink(): continue @@ -1668,25 +1671,20 @@ def _cumulative_accepted_dependency_paths( if str(candidate.get("id") or "") in chain_ids: continue related = candidate.get("related") if isinstance(candidate.get("related"), dict) else {} - result = candidate.get("result") if isinstance(candidate.get("result"), dict) else {} review = candidate.get("acceptance_review") if isinstance(candidate.get("acceptance_review"), dict) else {} frontier = review.get("repair_frontier") if isinstance(review.get("repair_frontier"), dict) else {} reset = review.get("review_reset") if isinstance(review.get("review_reset"), dict) else {} prior_id = frontier.get("prior_review_id") or reset.get("prior_review_id") if (related.get("plan") == plan_id and related.get("task") == dependency_id and isinstance(prior_id, str) and prior_id): - successor_edges.append(( - prior_id, - str(review.get("review_id") or ""), - result.get("state") == "completed" and review.get("verdict") == "accept", - )) + successor_edges.append((prior_id, str(review.get("review_id") or ""))) reachable = {final_review_id} while True: - additions = {current for prior, current, _ in successor_edges if prior in reachable} + additions = {current for prior, current in successor_edges if prior in reachable} if additions.issubset(reachable): break reachable.update(additions) - if any(accepted and prior in reachable for prior, _, accepted in successor_edges): + if any(prior in reachable for prior, _ in successor_edges): raise SystemExit("accepted dependency review_chain terminal result is stale") source_base = _resolve_commit(execution_root, str(base_identity.get("revision") or "")) diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 04b837e..2aec201 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -921,6 +921,24 @@ def review(review_id: str, index: int, verdict: str) -> dict[str, object]: "workspace": {"root": str(root)}, } + final_handoff_path = handoff_root / "handoff-final.yaml" + original_final_bytes = final_handoff_path.read_bytes() + same_reviewer_document = deepcopy(document) + same_reviewer_document["acceptance_review"]["reviewer"] = deepcopy( + accepted_repair["reviewer"] + ) + rendered = "\n".join(execution_context._dump_yaml(same_reviewer_document)) + "\n" + final_handoff_path.write_text( + rendered.replace(": none\n", ': "none"\n'), encoding="utf-8" + ) + same_reviewer = deepcopy(descriptor) + same_reviewer["review_chain"][-1]["handoff_sha256"] = hashlib.sha256( + final_handoff_path.read_bytes() + ).hexdigest() + with pytest.raises(SystemExit, match="fresh|reviewer|independent"): + execution_context._accepted_dependency_paths(task, root, [same_reviewer]) + final_handoff_path.write_bytes(original_final_bytes) + assert execution_context._accepted_dependency_paths(task, root, [descriptor]) == { dependency_path, repair_path, final_path } @@ -950,7 +968,7 @@ def review(review_id: str, index: int, verdict: str) -> dict[str, object]: commits.append(git(root, "rev-parse", "HEAD")) trees.append(git(root, "rev-parse", "HEAD^{tree}")) - later_review = review("review-later", 4, "accept") + later_review = review("review-later", 4, "repair") later_review["review_reset"] = { "prior_review_id": "review-final", "reason_class": "validation_allocation", @@ -959,12 +977,15 @@ def review(review_id: str, index: int, verdict: str) -> dict[str, object]: later_review["previous_review"] = { key: value for key, value in final_review.items() if key != "previous_review" } + later_review["findings"] = [deepcopy(initial_repair["findings"][0])] + later_review["findings"][0]["finding_id"] = "LATER-FINDING" + later_review["findings"][0]["target_identity"] = identity(4) later_path = handoff_root / "handoff-later.yaml" later_document = { "id": "handoff-later", "type": "executor-result", "related": {"plan": "plan-001", "task": "task-dependency"}, - "result": {"state": "completed"}, + "result": {"state": "partial"}, "acceptance_review": later_review, } rendered = "\n".join(execution_context._dump_yaml(later_document)) + "\n" From 77c525ab35cc123e1c2360ab8e664288f3ec12ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 09:08:28 +0800 Subject: [PATCH 22/36] feat(orchestration): add accepted-result recovery receipts --- scripts/orchestration/dispatcher.py | 17 +- scripts/orchestration/execution_context.py | 407 ++++++++++++++++++++- tests/test_wor108_context_projection.py | 302 +++++++++++++++ 3 files changed, 720 insertions(+), 6 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 94a3c8b..7cb739e 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -7,7 +7,13 @@ from core import HANDOFF_TYPES from doctor import cmd_doctor from documents import cmd_git_status, cmd_next_action_candidates, cmd_related, cmd_state, cmd_write_doc -from execution_context import cmd_build_review_package, cmd_build_task_brief, cmd_validate_executor_result, cmd_observe_task_validation +from execution_context import ( + cmd_build_review_package, + cmd_build_task_brief, + cmd_create_accepted_base_absence_receipt, + cmd_observe_task_validation, + cmd_validate_executor_result, +) from handoffs import cmd_index_handoffs, cmd_list_handoffs, cmd_set_handoff_status, cmd_write_handoff from init import cmd_init from plans import cmd_archive_plan, cmd_index_plans, cmd_list_plans, cmd_set_plan_status, cmd_write_phase, cmd_write_plan, cmd_write_task @@ -17,7 +23,8 @@ RECOGNIZED_COMMANDS = frozenset({ "init", "doctor", "state", "next-action-candidates", "git-status", "repository-preflight", "build-task-brief", "build-review-package", - "validate-executor-result", "observe-task-validation", "related", "write-doc", "write-spec", + "validate-executor-result", "observe-task-validation", "create-accepted-base-absence-receipt", + "related", "write-doc", "write-spec", "list-specs", "set-spec-status", "index-specs", "write-plan", "list-plans", "set-plan-status", "archive-plan", "index-plans", "write-phase", "write-task", "write-handoff", "list-handoffs", "set-handoff-status", "index-handoffs", @@ -83,6 +90,12 @@ def build_parser() -> argparse.ArgumentParser: observe_validation = sub.add_parser("observe-task-validation", parents=[parent]) observe_validation.add_argument("--task", required=True) observe_validation.set_defaults(func=cmd_observe_task_validation) + create_recovery_receipt = sub.add_parser( + "create-accepted-base-absence-receipt", parents=[parent] + ) + create_recovery_receipt.add_argument("--plan-id", required=True) + create_recovery_receipt.add_argument("--task-id", required=True) + create_recovery_receipt.set_defaults(func=cmd_create_accepted_base_absence_receipt) related = sub.add_parser("related", parents=[parent]) related.add_argument("--id", required=True) related.set_defaults(func=cmd_related) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 6c387aa..0d0f1a5 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1526,6 +1526,382 @@ def _review_without_history(review: Mapping[str, Any]) -> dict[str, Any]: return {key: value for key, value in review.items() if key != "previous_review"} +RECOVERY_RECEIPT_SCHEMA = "accepted-result-recovery-receipt-v1" +RECOVERY_RECEIPT_KEYS = { + "receipt_id", + "schema", + "plan_id", + "task_id", + "binding_id", + "binding_sha256", + "baseline_head", + "baseline_tree", + "queried_historical_revision", + "handoff_stores", + "handoff_index", + "absence_result", + "observed_at", + "freshness", +} + + +def _recovery_receipt_path( + control_root: Path, plan_id: str, task_id: str, receipt_id: str +) -> Path: + if not all(SAFE_ID_RE.fullmatch(value) for value in (plan_id, task_id, receipt_id)): + raise SystemExit("accepted-result recovery receipt identity is unsafe") + return ( + control_root.expanduser().resolve() + / ".work-bundle/runtime/execution" + / plan_id + / task_id + / "accepted-result-recovery" + / f"{receipt_id}.json" + ) + + +def _is_recoverable_accepted_base( + handoff: Mapping[str, Any], plan_id: str, task_id: str +) -> bool: + related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} + result = handoff.get("result") if isinstance(handoff.get("result"), Mapping) else {} + review = ( + handoff.get("acceptance_review") + if isinstance(handoff.get("acceptance_review"), Mapping) + else {} + ) + reset = review.get("review_reset") if isinstance(review.get("review_reset"), Mapping) else {} + if reset.get("reason_class") == "authority": + # This is the newly reconstructed whole-task result, never the missing + # historical accepted base whose absence authorizes reconstruction. + return False + if ( + handoff.get("type") != "executor-result" + or related.get("plan") != plan_id + or related.get("task") != task_id + or result.get("state") != "completed" + or review.get("verdict") != "accept" + ): + return False + try: + from review_runtime import ReviewContractError, validate_task_acceptance_review + + validate_task_acceptance_review(review) + except (ReviewContractError, KeyError, TypeError, ValueError): + return False + return True + + +def _accepted_base_query_snapshot( + control_root: Path, plan_id: str, task_id: str +) -> dict[str, Any]: + handoff_root = control_root / ".work-bundle/orchestration/handoff" + stores: dict[str, dict[str, Any]] = {} + recoverable_ids: list[str] = [] + for status in ("active", "archived"): + records: list[dict[str, str]] = [] + for path in sorted((handoff_root / "executor" / status).glob("*")): + if not path.is_file() or path.is_symlink(): + continue + try: + handoff, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} + if related.get("plan") != plan_id or related.get("task") != task_id: + continue + handoff_id = str(handoff.get("id") or "") + record = { + "handoff_id": handoff_id, + "path": path.relative_to(control_root).as_posix(), + "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + } + records.append(record) + if _is_recoverable_accepted_base(handoff, plan_id, task_id): + recoverable_ids.append(handoff_id) + stores[status] = { + "store_id": f"executor/{status}", + "records": records, + "sha256": semantic_digest(records), + } + + index_path = handoff_root / "index.jsonl" + if not index_path.is_file() or index_path.is_symlink(): + raise SystemExit("accepted-result recovery requires the native handoff index") + index_entries: list[dict[str, Any]] = [] + for number, line in enumerate(index_path.read_text(encoding="utf-8").splitlines(), 1): + if not line.strip(): + continue + try: + entry = json.loads(line) + except json.JSONDecodeError as error: + raise SystemExit(f"accepted-result recovery handoff index is invalid at line {number}") from error + if not isinstance(entry, dict): + raise SystemExit(f"accepted-result recovery handoff index is invalid at line {number}") + if entry.get("related_plan") == plan_id and entry.get("related_task") == task_id: + index_entries.append(entry) + index_identity = { + "index_id": "handoff/index.jsonl", + "path": index_path.relative_to(control_root).as_posix(), + "sha256": hashlib.sha256(index_path.read_bytes()).hexdigest(), + "projected_entries_sha256": semantic_digest(index_entries), + } + historical_revision = semantic_digest( + {"handoff_stores": stores, "handoff_index": index_identity} + ) + return { + "queried_historical_revision": historical_revision, + "handoff_stores": stores, + "handoff_index": index_identity, + "recoverable_base_handoff_ids": sorted(recoverable_ids), + } + + +def create_accepted_base_absence_receipt( + control_root: Path, plan_id: str, task_id: str +) -> dict[str, str]: + """Persist helper-observed proof that no native accepted-result base survives.""" + + control_root = control_root.expanduser().resolve() + binding_path = _binding_path(control_root, plan_id, task_id) + binding = load_task_execution_binding(control_root, plan_id, task_id) + baseline = binding.get("baseline") if isinstance(binding.get("baseline"), Mapping) else {} + baseline_head = str(baseline.get("head") or "") + baseline_tree = str(baseline.get("tree") or "") + if not baseline_head or not baseline_tree: + raise SystemExit("accepted-result recovery requires the original one-time task baseline") + snapshot = _accepted_base_query_snapshot(control_root, plan_id, task_id) + if snapshot["recoverable_base_handoff_ids"]: + raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") + observed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + receipt_id = ( + f"accepted-result-recovery-{task_id}-" + f"{snapshot['queried_historical_revision'][:12]}-" + f"{hashlib.sha256(observed_at.encode()).hexdigest()[:12]}" + ) + receipt = { + "receipt_id": receipt_id, + "schema": RECOVERY_RECEIPT_SCHEMA, + "plan_id": plan_id, + "task_id": task_id, + "binding_id": str((binding.get("ownership") or {}).get("binding_id") or ""), + "binding_sha256": hashlib.sha256(binding_path.read_bytes()).hexdigest(), + "baseline_head": baseline_head, + "baseline_tree": baseline_tree, + "queried_historical_revision": snapshot["queried_historical_revision"], + "handoff_stores": snapshot["handoff_stores"], + "handoff_index": snapshot["handoff_index"], + "absence_result": "accepted_base_absent", + "observed_at": observed_at, + "freshness": "current_validation_attempt", + } + path = _recovery_receipt_path(control_root, plan_id, task_id, receipt_id) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return { + "receipt_id": receipt_id, + "receipt_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + } + + +def validate_accepted_base_absence_receipt( + control_root: Path, + plan_id: str, + task_id: str, + reference: object, +) -> dict[str, Any]: + if not isinstance(reference, Mapping) or set(reference) != {"receipt_id", "receipt_sha256"}: + raise SystemExit("accepted-result recovery receipt reference must use the closed identity shape") + receipt_id = str(reference["receipt_id"]) + path = _recovery_receipt_path(control_root, plan_id, task_id, receipt_id) + if not path.is_file() or path.is_symlink(): + raise SystemExit("accepted-result recovery receipt is missing") + if hashlib.sha256(path.read_bytes()).hexdigest() != str(reference["receipt_sha256"]): + raise SystemExit("accepted-result recovery receipt is stale") + try: + receipt = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + raise SystemExit("accepted-result recovery receipt is invalid") from error + if not isinstance(receipt, dict) or set(receipt) != RECOVERY_RECEIPT_KEYS: + raise SystemExit("accepted-result recovery receipt must use the closed schema") + if ( + receipt.get("receipt_id") != receipt_id + or receipt.get("schema") != RECOVERY_RECEIPT_SCHEMA + or receipt.get("plan_id") != plan_id + or receipt.get("task_id") != task_id + or receipt.get("absence_result") != "accepted_base_absent" + or receipt.get("freshness") != "current_validation_attempt" + ): + raise SystemExit("accepted-result recovery receipt identity is mismatched") + binding_path = _binding_path(control_root, plan_id, task_id) + binding = load_task_execution_binding(control_root, plan_id, task_id) + baseline = binding.get("baseline") if isinstance(binding.get("baseline"), Mapping) else {} + if ( + receipt.get("binding_id") != (binding.get("ownership") or {}).get("binding_id") + or receipt.get("binding_sha256") != hashlib.sha256(binding_path.read_bytes()).hexdigest() + or receipt.get("baseline_head") != baseline.get("head") + or receipt.get("baseline_tree") != baseline.get("tree") + ): + raise SystemExit("accepted-result recovery receipt binding or baseline is stale") + snapshot = _accepted_base_query_snapshot(control_root, plan_id, task_id) + if snapshot["recoverable_base_handoff_ids"]: + raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") + for field in ("queried_historical_revision", "handoff_stores", "handoff_index"): + if receipt.get(field) != snapshot[field]: + raise SystemExit("accepted-result recovery receipt is stale") + return receipt + + +def _recovered_accepted_dependency_paths( + task: dict[str, Any], + execution_root: Path, + descriptors: list[Mapping[str, object]], +) -> set[str]: + required_fields = { + "task_id", + "execution_baseline_recovery", + "recovered_result", + "integrated_base", + "integrated_head", + } + recovery_fields = { + "binding_id", + "binding_sha256", + "baseline_head", + "baseline_tree", + "recovery_receipt", + } + dependencies = {str(value) for value in _as_list(task.get("depends_on"))} + plan_id = str(task.get("plan_id") or "") + workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} + control_root = Path(str(workspace.get("root") or "")).resolve() + handoff_root = control_root / ".work-bundle/orchestration/handoff" + current_head = _resolve_commit(execution_root, "HEAD") + admitted: set[str] = set() + seen_dependencies: set[str] = set() + for descriptor in descriptors: + if set(descriptor) != required_fields: + raise SystemExit( + "accepted_dependency_deltas recovery entries must use the closed runtime identity shape" + ) + dependency_id = str(descriptor["task_id"]) + if dependency_id not in dependencies: + raise SystemExit(f"accepted_dependency_deltas names undeclared dependency: {dependency_id}") + if dependency_id in seen_dependencies: + raise SystemExit(f"accepted_dependency_deltas duplicates dependency: {dependency_id}") + seen_dependencies.add(dependency_id) + recovery = descriptor["execution_baseline_recovery"] + if not isinstance(recovery, Mapping) or set(recovery) != recovery_fields: + raise SystemExit("execution_baseline_recovery must use the closed runtime identity shape") + + binding_path = _binding_path(control_root, plan_id, dependency_id) + binding = load_task_execution_binding(control_root, plan_id, dependency_id) + baseline = binding.get("baseline") if isinstance(binding.get("baseline"), Mapping) else {} + binding_id = str((binding.get("ownership") or {}).get("binding_id") or "") + binding_digest = hashlib.sha256(binding_path.read_bytes()).hexdigest() + if ( + recovery.get("binding_id") != binding_id + or recovery.get("binding_sha256") != binding_digest + or recovery.get("baseline_head") != baseline.get("head") + or recovery.get("baseline_tree") != baseline.get("tree") + ): + raise SystemExit("execution_baseline_recovery binding or original baseline is mismatched") + validate_accepted_base_absence_receipt( + control_root, + plan_id, + dependency_id, + recovery.get("recovery_receipt"), + ) + + _, recovered = _exact_handoff_reference(handoff_root, descriptor["recovered_result"]) + related = recovered.get("related") if isinstance(recovered.get("related"), Mapping) else {} + result = recovered.get("result") if isinstance(recovered.get("result"), Mapping) else {} + review = ( + recovered.get("acceptance_review") + if isinstance(recovered.get("acceptance_review"), Mapping) + else {} + ) + reset = review.get("review_reset") if isinstance(review.get("review_reset"), Mapping) else {} + evidence = review.get("evidence") if isinstance(review.get("evidence"), Mapping) else {} + reviewer = review.get("reviewer") if isinstance(review.get("reviewer"), Mapping) else {} + identity = review.get("target_identity") if isinstance(review.get("target_identity"), Mapping) else {} + if ( + related.get("plan") != plan_id + or related.get("task") != dependency_id + or result.get("state") != "completed" + or review.get("verdict") != "accept" + or review.get("review_mode") != "initial" + or review.get("review_target_kind") != "task" + or review.get("reviewer_independent") is not True + or review.get("repair_frontier") is not None + or reset.get("reason_class") != "authority" + or evidence.get("unavailable_evidence") != [] + or reviewer.get("capability") != "judgment" + or identity.get("artifact_id") != dependency_id + or review.get("reviewed_head") != identity.get("revision") + ): + raise SystemExit( + "recovered result requires a fresh complete independent whole-task initial authority review with empty unavailable_evidence" + ) + try: + from review_runtime import ReviewContractError, validate_task_acceptance_review + + validate_task_acceptance_review(review) + except ReviewContractError as error: + raise SystemExit(f"recovered result task review is invalid: {error}") from error + try: + owner = normalize_subagent_provenance( + recovered.get("delegation_evidence") + if isinstance(recovered.get("delegation_evidence"), Mapping) + else None + ) + except OwnershipBlocker as error: + raise SystemExit(f"recovered result ownership is invalid: {error}") from error + if reviewer.get("agent_id") == owner.get("agent_id"): + raise SystemExit("recovered result reviewer is not independent from the task owner") + + source_base = _resolve_commit(execution_root, str(baseline.get("head") or "")) + source_head = _resolve_commit(execution_root, str(identity.get("revision") or "")) + source_tree = _git(execution_root, "rev-parse", f"{source_head}^{{tree}}").strip() + if baseline.get("tree") != _git(execution_root, "rev-parse", f"{source_base}^{{tree}}").strip(): + raise SystemExit("execution_baseline_recovery baseline Git identity is mismatched") + if identity.get("source_tree") != source_tree: + raise SystemExit("recovered result Git identity is mismatched") + if subprocess.run( + ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", source_base, source_head], + capture_output=True, + check=False, + ).returncode: + raise SystemExit("recovered result source chain is non-ancestral") + integrated_base = _resolve_commit(execution_root, str(descriptor["integrated_base"])) + integrated_head = _resolve_commit(execution_root, str(descriptor["integrated_head"])) + if subprocess.run( + ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", integrated_head, current_head], + capture_output=True, + check=False, + ).returncode: + raise SystemExit("recovered result integration checkpoint is not current") + source_diff = _git(execution_root, "diff", "--binary", source_base, source_head, "--") + integrated_diff = _git( + execution_root, "diff", "--binary", integrated_base, integrated_head, "--" + ) + if source_diff != integrated_diff: + raise SystemExit("recovered result integration checkpoint is mismatched") + paths = { + path + for line in _git( + execution_root, "diff", "--name-status", source_base, source_head, "--" + ).splitlines() + for path in _paths_from_name_status(line) + } + if paths and _git( + execution_root, "diff", "--name-only", integrated_head, "--", *sorted(paths) + ).strip(): + raise SystemExit("recovered dependency path changed after integration") + admitted.update(paths) + return admitted + + def _cumulative_accepted_dependency_paths( task: dict[str, Any], execution_root: Path, @@ -1733,10 +2109,25 @@ def _accepted_dependency_paths( cumulative_fields = { "task_id", "accepted_result_base", "review_chain", "integrated_base", "integrated_head" } - if all(isinstance(item, Mapping) and set(item) == cumulative_fields for item in descriptors): - return _cumulative_accepted_dependency_paths(task, execution_root, descriptors) - if any(isinstance(item, Mapping) and set(item) == cumulative_fields for item in descriptors): - raise SystemExit("accepted_dependency_deltas cannot mix cumulative and legacy identities") + recovery_fields = { + "task_id", "execution_baseline_recovery", "recovered_result", "integrated_base", "integrated_head" + } + cumulative = [ + item for item in descriptors if isinstance(item, Mapping) and set(item) == cumulative_fields + ] + recovered = [ + item for item in descriptors if isinstance(item, Mapping) and set(item) == recovery_fields + ] + if cumulative or recovered: + if len(cumulative) + len(recovered) != len(descriptors): + raise SystemExit("accepted_dependency_deltas cannot mix closed and legacy identities") + dependency_ids = [str(item["task_id"]) for item in [*cumulative, *recovered]] + if len(dependency_ids) != len(set(dependency_ids)): + raise SystemExit("accepted_dependency_deltas duplicates dependency") + return ( + _cumulative_accepted_dependency_paths(task, execution_root, cumulative) + | _recovered_accepted_dependency_paths(task, execution_root, recovered) + ) dependencies = {str(value) for value in _as_list(task.get("depends_on"))} plan_id = str(task.get("plan_id") or "") workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} @@ -3178,6 +3569,14 @@ def cmd_validate_executor_result(args: argparse.Namespace) -> None: print(handoff_path.relative_to(root).as_posix()) +def cmd_create_accepted_base_absence_receipt(args: argparse.Namespace) -> None: + root = resolve_workspace_root(args) + reference = create_accepted_base_absence_receipt( + root, str(args.plan_id), str(args.task_id) + ) + print(json.dumps(reference, sort_keys=True)) + + def _observation_kwargs(args: argparse.Namespace) -> dict[str, Any]: return { "workspace_id": getattr(args, "workspace_id", None) or None, diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 2aec201..cca9494 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -994,6 +994,308 @@ def review(review_id: str, index: int, verdict: str) -> dict[str, object]: execution_context._accepted_dependency_paths(task, root, [descriptor]) +def test_authority_recovery_receipt_is_helper_created_fresh_and_rechecked( + tmp_path: Path, +) -> None: + root, _, task_path = workspace(tmp_path) + dependency_path = "references/assets/orchestration/workflow.md" + dependency = root / dependency_path + dependency.parent.mkdir(parents=True, exist_ok=True) + dependency.write_text("original accepted baseline\n", encoding="utf-8") + git(root, "add", ".") + git(root, "commit", "-qm", "original dependency baseline") + baseline = git(root, "rev-parse", "HEAD") + baseline_tree = git(root, "rev-parse", "HEAD^{tree}") + + dependent = _compiled_brief(root, task_path) + dependency_brief = deepcopy(dependent) + dependency_brief["task_id"] = "task-dependency" + binding = _bind_task_execution( + root, + dependency_brief, + execution_id="dependency-exec", + write_scope=[dependency_path], + ) + binding_path = ( + root + / ".work-bundle/runtime/execution/plan-001/task-dependency/execution-binding.json" + ) + binding_digest = hashlib.sha256(binding_path.read_bytes()).hexdigest() + + dependency.write_text("fresh whole-task accepted result\n", encoding="utf-8") + git(root, "add", dependency_path) + git(root, "commit", "-qm", "fresh recovered dependency result") + recovered_head = git(root, "rev-parse", "HEAD") + recovered_tree = git(root, "rev-parse", "HEAD^{tree}") + + def identity(commit: str, tree: str) -> dict[str, object]: + return { + "artifact_id": "task-dependency", + "revision": commit, + "sha256": execution_context.semantic_digest({"commit": commit, "tree": tree}), + "source_tree": tree, + } + + def reviewer(agent_id: str) -> dict[str, object]: + return { + "agent_id": agent_id, + "capability": "judgment", + "authorship": "none", + "repair_participation": "none", + "decision_participation": "none", + "deliberation_participation": "none", + "context_origin": "direct_source", + } + + previous = { + "required": True, + "reviewer_independent": True, + "verdict": "accept", + "reviewed_head": baseline, + "review_id": "review-dependency-accepted-historical", + "review_mode": "initial", + "review_target_kind": "task", + "repair_frontier": None, + "review_reset": None, + "target_identity": identity(baseline, baseline_tree), + "reviewer": reviewer("historical-reviewer"), + "evidence": { + "mode": "direct", + "capabilities": ["whole-task source review"], + "unavailable_evidence": [], + "commands": [], + "artifacts": [], + }, + "findings": [], + "started_at": "2026-09-06T01:00:00Z", + "completed_at": "2026-09-06T01:01:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + recovered_review = { + **deepcopy(previous), + "review_id": "review-dependency-authority-recovery", + "reviewed_head": recovered_head, + "target_identity": identity(recovered_head, recovered_tree), + "reviewer": reviewer("fresh-recovery-reviewer"), + "review_reset": { + "prior_review_id": previous["review_id"], + "reason_class": "authority", + "reason": "The accepted-result handoff bytes are unavailable.", + }, + "previous_review": previous, + "started_at": "2026-09-06T01:02:00Z", + "completed_at": "2026-09-06T01:03:00Z", + } + recovered_handoff = { + "id": "handoff-recovered-dependency", + "type": "executor-result", + "status": "active", + "project": "fixture", + "created_at": "2026-09-06", + "updated_at": "2026-09-06", + "related": {"plan": "plan-001", "task": "task-dependency"}, + "result": {"state": "completed"}, + "delegation_evidence": { + "delegated": True, + "owner_kind": "subagent", + "agent_id": "dependency-owner", + "run_id": "dependency-run", + "mechanism": "host-native", + }, + "acceptance_review": recovered_review, + } + handoff_dir = root / ".work-bundle/orchestration/handoff/executor/active" + handoff_dir.mkdir(parents=True, exist_ok=True) + recovered_path = handoff_dir / "handoff-recovered-dependency.yaml" + recovered_path.write_text( + ("\n".join(execution_context._dump_yaml(recovered_handoff)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + index = root / ".work-bundle/orchestration/handoff/index.jsonl" + index.write_text( + json.dumps({ + "id": recovered_handoff["id"], + "type": "executor-result", + "status": "active", + "path": str(recovered_path.relative_to(root)), + "related_plan": "plan-001", + "related_task": "task-dependency", + }) + "\n", + encoding="utf-8", + ) + + create_receipt = subprocess.run( + [ + sys.executable, + str(REPO_ROOT / "scripts/orch.py"), + "create-accepted-base-absence-receipt", + "--project-root", + str(root), + "--plan-id", + "plan-001", + "--task-id", + "task-dependency", + ], + capture_output=True, + text=True, + ) + assert create_receipt.returncode == 0, create_receipt.stderr + receipt_reference = json.loads(create_receipt.stdout) + assert set(receipt_reference) == {"receipt_id", "receipt_sha256"} + receipt_path = execution_context._recovery_receipt_path( + root, "plan-001", "task-dependency", receipt_reference["receipt_id"] + ) + receipt = json.loads(receipt_path.read_text(encoding="utf-8")) + assert receipt["schema"] == "accepted-result-recovery-receipt-v1" + assert receipt["binding_id"] == binding["ownership"]["binding_id"] + assert receipt["binding_sha256"] == binding_digest + assert receipt["baseline_head"] == baseline + assert receipt["baseline_tree"] == baseline_tree + assert receipt["absence_result"] == "accepted_base_absent" + assert receipt["freshness"] == "current_validation_attempt" + assert set(receipt["handoff_stores"]) == {"active", "archived"} + + recovered_reference = { + "handoff_id": recovered_handoff["id"], + "handoff_sha256": hashlib.sha256(recovered_path.read_bytes()).hexdigest(), + } + descriptor = { + "task_id": "task-dependency", + "execution_baseline_recovery": { + "binding_id": binding["ownership"]["binding_id"], + "binding_sha256": binding_digest, + "baseline_head": baseline, + "baseline_tree": baseline_tree, + "recovery_receipt": receipt_reference, + }, + "recovered_result": recovered_reference, + "integrated_base": baseline, + "integrated_head": recovered_head, + } + dependent["depends_on"] = ["task-dependency"] + assert execution_context._accepted_dependency_paths(dependent, root, [descriptor]) == { + dependency_path + } + + caller_assertion = deepcopy(descriptor) + caller_assertion["execution_baseline_recovery"]["accepted_base_absent"] = True + with pytest.raises(SystemExit, match="closed|shape"): + execution_context._accepted_dependency_paths(dependent, root, [caller_assertion]) + + recovered_review_with_gap = deepcopy(recovered_review) + recovered_review_with_gap["evidence"]["unavailable_evidence"] = ["historical handoff"] + recovered_handoff_with_gap = deepcopy(recovered_handoff) + recovered_handoff_with_gap["acceptance_review"] = recovered_review_with_gap + recovered_path.write_text( + ("\n".join(execution_context._dump_yaml(recovered_handoff_with_gap)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + gap_receipt = execution_context.create_accepted_base_absence_receipt( + root, "plan-001", "task-dependency" + ) + with pytest.raises(SystemExit, match="unavailable_evidence|complete"): + execution_context._accepted_dependency_paths(dependent, root, [{ + **descriptor, + "execution_baseline_recovery": { + **descriptor["execution_baseline_recovery"], + "recovery_receipt": gap_receipt, + }, + "recovered_result": { + **recovered_reference, + "handoff_sha256": hashlib.sha256(recovered_path.read_bytes()).hexdigest(), + }, + }]) + recovered_path.write_text( + ("\n".join(execution_context._dump_yaml(recovered_handoff)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + + extra = handoff_dir / "unrelated-target-record.yaml" + extra.write_text( + "id: unrelated-target-record\ntype: executor-result\n" + "related: {plan: plan-001, task: task-dependency}\nresult: {state: partial}\n", + encoding="utf-8", + ) + with pytest.raises(SystemExit, match="stale"): + execution_context._accepted_dependency_paths(dependent, root, [descriptor]) + + +def test_authority_recovery_receipt_rejects_recoverable_accepted_base(tmp_path: Path) -> None: + root, _, task_path = workspace(tmp_path) + git(root, "add", ".") + git(root, "commit", "-qm", "baseline") + brief = _compiled_brief(root, task_path) + brief["task_id"] = "task-dependency" + _bind_task_execution(root, brief, execution_id="dependency-exec") + handoff_dir = root / ".work-bundle/orchestration/handoff/executor/archived" + handoff_dir.mkdir(parents=True, exist_ok=True) + commit = git(root, "rev-parse", "HEAD") + tree = git(root, "rev-parse", "HEAD^{tree}") + accepted = { + "id": "handoff-historical-accepted-base", + "type": "executor-result", + "related": {"plan": "plan-001", "task": "task-dependency"}, + "result": {"state": "completed"}, + "acceptance_review": { + "required": True, + "reviewer_independent": True, + "verdict": "accept", + "reviewed_head": commit, + "review_id": "review-historical-accepted-base", + "review_mode": "initial", + "review_target_kind": "task", + "repair_frontier": None, + "review_reset": None, + "target_identity": { + "artifact_id": "task-dependency", + "revision": commit, + "sha256": execution_context.semantic_digest({"commit": commit, "tree": tree}), + "source_tree": tree, + }, + "reviewer": { + "agent_id": "historical-reviewer", + "capability": "judgment", + "authorship": "none", + "repair_participation": "none", + "decision_participation": "none", + "deliberation_participation": "none", + "context_origin": "direct_source", + }, + "evidence": { + "mode": "direct", + "capabilities": ["whole-task source review"], + "unavailable_evidence": [], + "commands": [], + "artifacts": [], + }, + "findings": [], + "started_at": "2026-09-06T01:00:00Z", + "completed_at": "2026-09-06T01:01:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + }, + } + accepted_path = handoff_dir / "handoff-historical-accepted-base.yaml" + accepted_path.write_text( + ("\n".join(execution_context._dump_yaml(accepted)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + index = root / ".work-bundle/orchestration/handoff/index.jsonl" + index.write_text("", encoding="utf-8") + + with pytest.raises(SystemExit, match="recoverable accepted base"): + execution_context.create_accepted_base_absence_receipt( + root, "plan-001", "task-dependency" + ) + + def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( tmp_path: Path, ) -> None: From d0e42c20f4f0237abdfa2820e0ea2757f16db99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 09:15:06 +0800 Subject: [PATCH 23/36] fix(orchestration): scope recovery lookup to expected base --- scripts/orchestration/dispatcher.py | 2 + scripts/orchestration/execution_context.py | 79 +++++++++++++++++++--- tests/test_wor108_context_projection.py | 25 ++++++- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 7cb739e..3689e6a 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -95,6 +95,8 @@ def build_parser() -> argparse.ArgumentParser: ) create_recovery_receipt.add_argument("--plan-id", required=True) create_recovery_receipt.add_argument("--task-id", required=True) + create_recovery_receipt.add_argument("--expected-head", required=True) + create_recovery_receipt.add_argument("--expected-tree", required=True) create_recovery_receipt.set_defaults(func=cmd_create_accepted_base_absence_receipt) related = sub.add_parser("related", parents=[parent]) related.add_argument("--id", required=True) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 0d0f1a5..cd84534 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1536,6 +1536,8 @@ def _review_without_history(review: Mapping[str, Any]) -> dict[str, Any]: "binding_sha256", "baseline_head", "baseline_tree", + "expected_base_head", + "expected_base_tree", "queried_historical_revision", "handoff_stores", "handoff_index", @@ -1561,7 +1563,11 @@ def _recovery_receipt_path( def _is_recoverable_accepted_base( - handoff: Mapping[str, Any], plan_id: str, task_id: str + handoff: Mapping[str, Any], + plan_id: str, + task_id: str, + expected_base_head: str, + expected_base_tree: str, ) -> bool: related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} result = handoff.get("result") if isinstance(handoff.get("result"), Mapping) else {} @@ -1583,6 +1589,14 @@ def _is_recoverable_accepted_base( or review.get("verdict") != "accept" ): return False + identity = review.get("target_identity") if isinstance(review.get("target_identity"), Mapping) else {} + if ( + identity.get("artifact_id") != task_id + or identity.get("revision") != expected_base_head + or identity.get("source_tree") != expected_base_tree + or review.get("reviewed_head") != expected_base_head + ): + return False try: from review_runtime import ReviewContractError, validate_task_acceptance_review @@ -1593,7 +1607,11 @@ def _is_recoverable_accepted_base( def _accepted_base_query_snapshot( - control_root: Path, plan_id: str, task_id: str + control_root: Path, + plan_id: str, + task_id: str, + expected_base_head: str, + expected_base_tree: str, ) -> dict[str, Any]: handoff_root = control_root / ".work-bundle/orchestration/handoff" stores: dict[str, dict[str, Any]] = {} @@ -1617,7 +1635,13 @@ def _accepted_base_query_snapshot( "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), } records.append(record) - if _is_recoverable_accepted_base(handoff, plan_id, task_id): + if _is_recoverable_accepted_base( + handoff, + plan_id, + task_id, + expected_base_head, + expected_base_tree, + ): recoverable_ids.append(handoff_id) stores[status] = { "store_id": f"executor/{status}", @@ -1647,7 +1671,12 @@ def _accepted_base_query_snapshot( "projected_entries_sha256": semantic_digest(index_entries), } historical_revision = semantic_digest( - {"handoff_stores": stores, "handoff_index": index_identity} + { + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, + "handoff_stores": stores, + "handoff_index": index_identity, + } ) return { "queried_historical_revision": historical_revision, @@ -1658,7 +1687,11 @@ def _accepted_base_query_snapshot( def create_accepted_base_absence_receipt( - control_root: Path, plan_id: str, task_id: str + control_root: Path, + plan_id: str, + task_id: str, + expected_base_head: str, + expected_base_tree: str, ) -> dict[str, str]: """Persist helper-observed proof that no native accepted-result base survives.""" @@ -1670,7 +1703,20 @@ def create_accepted_base_absence_receipt( baseline_tree = str(baseline.get("tree") or "") if not baseline_head or not baseline_tree: raise SystemExit("accepted-result recovery requires the original one-time task baseline") - snapshot = _accepted_base_query_snapshot(control_root, plan_id, task_id) + execution_root = Path(str(binding.get("execution_path") or "")).resolve() + resolved_expected_head = _resolve_commit(execution_root, expected_base_head) + resolved_expected_tree = _git( + execution_root, "rev-parse", f"{resolved_expected_head}^{{tree}}" + ).strip() + if resolved_expected_tree != expected_base_tree: + raise SystemExit("accepted-result recovery expected base Git identity is mismatched") + snapshot = _accepted_base_query_snapshot( + control_root, + plan_id, + task_id, + resolved_expected_head, + resolved_expected_tree, + ) if snapshot["recoverable_base_handoff_ids"]: raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") observed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") @@ -1688,6 +1734,8 @@ def create_accepted_base_absence_receipt( "binding_sha256": hashlib.sha256(binding_path.read_bytes()).hexdigest(), "baseline_head": baseline_head, "baseline_tree": baseline_tree, + "expected_base_head": resolved_expected_head, + "expected_base_tree": resolved_expected_tree, "queried_historical_revision": snapshot["queried_historical_revision"], "handoff_stores": snapshot["handoff_stores"], "handoff_index": snapshot["handoff_index"], @@ -1743,7 +1791,18 @@ def validate_accepted_base_absence_receipt( or receipt.get("baseline_tree") != baseline.get("tree") ): raise SystemExit("accepted-result recovery receipt binding or baseline is stale") - snapshot = _accepted_base_query_snapshot(control_root, plan_id, task_id) + execution_root = Path(str(binding.get("execution_path") or "")).resolve() + expected_head = _resolve_commit(execution_root, str(receipt.get("expected_base_head") or "")) + expected_tree = _git(execution_root, "rev-parse", f"{expected_head}^{{tree}}").strip() + if receipt.get("expected_base_tree") != expected_tree: + raise SystemExit("accepted-result recovery receipt expected base identity is stale") + snapshot = _accepted_base_query_snapshot( + control_root, + plan_id, + task_id, + expected_head, + expected_tree, + ) if snapshot["recoverable_base_handoff_ids"]: raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") for field in ("queried_historical_revision", "handoff_stores", "handoff_index"): @@ -3572,7 +3631,11 @@ def cmd_validate_executor_result(args: argparse.Namespace) -> None: def cmd_create_accepted_base_absence_receipt(args: argparse.Namespace) -> None: root = resolve_workspace_root(args) reference = create_accepted_base_absence_receipt( - root, str(args.plan_id), str(args.task_id) + root, + str(args.plan_id), + str(args.task_id), + str(args.expected_head), + str(args.expected_tree), ) print(json.dumps(reference, sort_keys=True)) diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index cca9494..75bc446 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -1137,6 +1137,10 @@ def reviewer(agent_id: str) -> dict[str, object]: "plan-001", "--task-id", "task-dependency", + "--expected-head", + baseline, + "--expected-tree", + baseline_tree, ], capture_output=True, text=True, @@ -1195,7 +1199,7 @@ def reviewer(agent_id: str) -> dict[str, object]: encoding="utf-8", ) gap_receipt = execution_context.create_accepted_base_absence_receipt( - root, "plan-001", "task-dependency" + root, "plan-001", "task-dependency", baseline, baseline_tree ) with pytest.raises(SystemExit, match="unavailable_evidence|complete"): execution_context._accepted_dependency_paths(dependent, root, [{ @@ -1292,9 +1296,26 @@ def test_authority_recovery_receipt_rejects_recoverable_accepted_base(tmp_path: with pytest.raises(SystemExit, match="recoverable accepted base"): execution_context.create_accepted_base_absence_receipt( - root, "plan-001", "task-dependency" + root, "plan-001", "task-dependency", commit, tree ) + distinct = root / "tests/distinct-expected-base.txt" + distinct.parent.mkdir(parents=True, exist_ok=True) + distinct.write_text("distinct expected identity\n", encoding="utf-8") + git(root, "add", str(distinct.relative_to(root))) + git(root, "commit", "-qm", "distinct expected base identity") + distinct_head = git(root, "rev-parse", "HEAD") + distinct_tree = git(root, "rev-parse", "HEAD^{tree}") + reference = execution_context.create_accepted_base_absence_receipt( + root, "plan-001", "task-dependency", distinct_head, distinct_tree + ) + receipt_path = execution_context._recovery_receipt_path( + root, "plan-001", "task-dependency", reference["receipt_id"] + ) + receipt = json.loads(receipt_path.read_text(encoding="utf-8")) + assert receipt["expected_base_head"] == distinct_head + assert receipt["expected_base_tree"] == distinct_tree + def test_rf_07_brief_rebuild_retains_original_execution_binding_and_baseline( tmp_path: Path, From cd162e593bf1d1b8c043fda4a18fc91f312d693e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 10:01:45 +0800 Subject: [PATCH 24/36] fix(orchestration): scope recovery receipts to proposals --- scripts/orchestration/dispatcher.py | 4 + scripts/orchestration/execution_context.py | 215 +++++++++++++++------ tests/test_wor108_context_projection.py | 164 ++++++++++++---- 3 files changed, 288 insertions(+), 95 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 3689e6a..5488d9b 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -97,6 +97,10 @@ def build_parser() -> argparse.ArgumentParser: create_recovery_receipt.add_argument("--task-id", required=True) create_recovery_receipt.add_argument("--expected-head", required=True) create_recovery_receipt.add_argument("--expected-tree", required=True) + create_recovery_receipt.add_argument("--proposed-handoff-id", required=True) + create_recovery_receipt.add_argument("--proposed-review-id", required=True) + create_recovery_receipt.add_argument("--final-head", required=True) + create_recovery_receipt.add_argument("--final-tree", required=True) create_recovery_receipt.set_defaults(func=cmd_create_accepted_base_absence_receipt) related = sub.add_parser("related", parents=[parent]) related.add_argument("--id", required=True) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index cd84534..ff5326e 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1538,10 +1538,8 @@ def _review_without_history(review: Mapping[str, Any]) -> dict[str, Any]: "baseline_tree", "expected_base_head", "expected_base_tree", - "queried_historical_revision", - "handoff_stores", - "handoff_index", - "absence_result", + "expected_base_query", + "proposed_recovered_result", "observed_at", "freshness", } @@ -1612,12 +1610,15 @@ def _accepted_base_query_snapshot( task_id: str, expected_base_head: str, expected_base_tree: str, + proposed_handoff_id: str, + proposed_review_id: str, + final_head: str, + final_tree: str, ) -> dict[str, Any]: handoff_root = control_root / ".work-bundle/orchestration/handoff" - stores: dict[str, dict[str, Any]] = {} recoverable_ids: list[str] = [] + proposal_records: list[dict[str, Any]] = [] for status in ("active", "archived"): - records: list[dict[str, str]] = [] for path in sorted((handoff_root / "executor" / status).glob("*")): if not path.is_file() or path.is_symlink(): continue @@ -1625,16 +1626,7 @@ def _accepted_base_query_snapshot( handoff, _ = _read_structured(path) except (OSError, SystemExit, ValueError): continue - related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} - if related.get("plan") != plan_id or related.get("task") != task_id: - continue handoff_id = str(handoff.get("id") or "") - record = { - "handoff_id": handoff_id, - "path": path.relative_to(control_root).as_posix(), - "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), - } - records.append(record) if _is_recoverable_accepted_base( handoff, plan_id, @@ -1643,16 +1635,45 @@ def _accepted_base_query_snapshot( expected_base_tree, ): recoverable_ids.append(handoff_id) - stores[status] = { - "store_id": f"executor/{status}", - "records": records, - "sha256": semantic_digest(records), - } + related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} + review = ( + handoff.get("acceptance_review") + if isinstance(handoff.get("acceptance_review"), Mapping) + else {} + ) + identity = ( + review.get("target_identity") + if isinstance(review.get("target_identity"), Mapping) + else {} + ) + if handoff_id != proposed_handoff_id and review.get("review_id") != proposed_review_id: + continue + result = handoff.get("result") if isinstance(handoff.get("result"), Mapping) else {} + proposal_records.append( + { + "handoff_id": handoff_id, + "review_id": str(review.get("review_id") or ""), + "path": path.relative_to(control_root).as_posix(), + "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + "exact": ( + handoff_id == proposed_handoff_id + and handoff.get("type") == "executor-result" + and related.get("plan") == plan_id + and related.get("task") == task_id + and result.get("state") == "completed" + and review.get("review_id") == proposed_review_id + and review.get("reviewed_head") == final_head + and identity.get("artifact_id") == task_id + and identity.get("revision") == final_head + and identity.get("source_tree") == final_tree + ), + } + ) index_path = handoff_root / "index.jsonl" if not index_path.is_file() or index_path.is_symlink(): raise SystemExit("accepted-result recovery requires the native handoff index") - index_entries: list[dict[str, Any]] = [] + proposal_index_entries: list[dict[str, Any]] = [] for number, line in enumerate(index_path.read_text(encoding="utf-8").splitlines(), 1): if not line.strip(): continue @@ -1662,27 +1683,41 @@ def _accepted_base_query_snapshot( raise SystemExit(f"accepted-result recovery handoff index is invalid at line {number}") from error if not isinstance(entry, dict): raise SystemExit(f"accepted-result recovery handoff index is invalid at line {number}") - if entry.get("related_plan") == plan_id and entry.get("related_task") == task_id: - index_entries.append(entry) - index_identity = { - "index_id": "handoff/index.jsonl", - "path": index_path.relative_to(control_root).as_posix(), - "sha256": hashlib.sha256(index_path.read_bytes()).hexdigest(), - "projected_entries_sha256": semantic_digest(index_entries), + if entry.get("id") == proposed_handoff_id: + proposal_index_entries.append(entry) + + exact_records = [record for record in proposal_records if record["exact"]] + proposal_state = "absent" + if proposal_records or proposal_index_entries: + if len(proposal_records) != 1 or len(proposal_index_entries) != 1: + proposal_state = "ambiguous" + elif not exact_records: + proposal_state = "mismatch" + else: + proposal_path = str(exact_records[0]["path"]) + index_entry = proposal_index_entries[0] + if ( + index_entry.get("related_plan") != plan_id + or index_entry.get("related_task") != task_id + or index_entry.get("path") != proposal_path + ): + proposal_state = "mismatch" + else: + proposal_state = "published" + + query_identity = { + "plan_id": plan_id, + "task_id": task_id, + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, } - historical_revision = semantic_digest( - { - "expected_base_head": expected_base_head, - "expected_base_tree": expected_base_tree, - "handoff_stores": stores, - "handoff_index": index_identity, - } - ) return { - "queried_historical_revision": historical_revision, - "handoff_stores": stores, - "handoff_index": index_identity, + "expected_base_query": { + "sha256": semantic_digest(query_identity), + "result": "present" if recoverable_ids else "absent", + }, "recoverable_base_handoff_ids": sorted(recoverable_ids), + "proposal_state": proposal_state, } @@ -1692,6 +1727,10 @@ def create_accepted_base_absence_receipt( task_id: str, expected_base_head: str, expected_base_tree: str, + proposed_handoff_id: str, + proposed_review_id: str, + final_head: str, + final_tree: str, ) -> dict[str, str]: """Persist helper-observed proof that no native accepted-result base survives.""" @@ -1703,6 +1742,8 @@ def create_accepted_base_absence_receipt( baseline_tree = str(baseline.get("tree") or "") if not baseline_head or not baseline_tree: raise SystemExit("accepted-result recovery requires the original one-time task baseline") + if not SAFE_ID_RE.fullmatch(proposed_handoff_id) or not SAFE_ID_RE.fullmatch(proposed_review_id): + raise SystemExit("accepted-result recovery proposed identity is unsafe") execution_root = Path(str(binding.get("execution_path") or "")).resolve() resolved_expected_head = _resolve_commit(execution_root, expected_base_head) resolved_expected_tree = _git( @@ -1710,19 +1751,29 @@ def create_accepted_base_absence_receipt( ).strip() if resolved_expected_tree != expected_base_tree: raise SystemExit("accepted-result recovery expected base Git identity is mismatched") + resolved_final_head = _resolve_commit(execution_root, final_head) + resolved_final_tree = _git(execution_root, "rev-parse", f"{resolved_final_head}^{{tree}}").strip() + if resolved_final_tree != final_tree: + raise SystemExit("accepted-result recovery proposed final Git identity is mismatched") snapshot = _accepted_base_query_snapshot( control_root, plan_id, task_id, resolved_expected_head, resolved_expected_tree, + proposed_handoff_id, + proposed_review_id, + resolved_final_head, + resolved_final_tree, ) if snapshot["recoverable_base_handoff_ids"]: raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") + if snapshot["proposal_state"] != "absent": + raise SystemExit("accepted-result recovery rejected: proposed result already exists or is ambiguous") observed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") receipt_id = ( f"accepted-result-recovery-{task_id}-" - f"{snapshot['queried_historical_revision'][:12]}-" + f"{snapshot['expected_base_query']['sha256'][:12]}-" f"{hashlib.sha256(observed_at.encode()).hexdigest()[:12]}" ) receipt = { @@ -1736,10 +1787,13 @@ def create_accepted_base_absence_receipt( "baseline_tree": baseline_tree, "expected_base_head": resolved_expected_head, "expected_base_tree": resolved_expected_tree, - "queried_historical_revision": snapshot["queried_historical_revision"], - "handoff_stores": snapshot["handoff_stores"], - "handoff_index": snapshot["handoff_index"], - "absence_result": "accepted_base_absent", + "expected_base_query": snapshot["expected_base_query"], + "proposed_recovered_result": { + "handoff_id": proposed_handoff_id, + "review_id": proposed_review_id, + "final_head": resolved_final_head, + "final_tree": resolved_final_tree, + }, "observed_at": observed_at, "freshness": "current_validation_attempt", } @@ -1777,7 +1831,6 @@ def validate_accepted_base_absence_receipt( or receipt.get("schema") != RECOVERY_RECEIPT_SCHEMA or receipt.get("plan_id") != plan_id or receipt.get("task_id") != task_id - or receipt.get("absence_result") != "accepted_base_absent" or receipt.get("freshness") != "current_validation_attempt" ): raise SystemExit("accepted-result recovery receipt identity is mismatched") @@ -1796,19 +1849,33 @@ def validate_accepted_base_absence_receipt( expected_tree = _git(execution_root, "rev-parse", f"{expected_head}^{{tree}}").strip() if receipt.get("expected_base_tree") != expected_tree: raise SystemExit("accepted-result recovery receipt expected base identity is stale") + proposal = receipt.get("proposed_recovered_result") + if not isinstance(proposal, Mapping) or set(proposal) != { + "handoff_id", "review_id", "final_head", "final_tree" + }: + raise SystemExit("accepted-result recovery receipt proposed result is invalid") + final_head = _resolve_commit(execution_root, str(proposal.get("final_head") or "")) + final_tree = _git(execution_root, "rev-parse", f"{final_head}^{{tree}}").strip() + if proposal.get("final_tree") != final_tree: + raise SystemExit("accepted-result recovery receipt proposed final identity is stale") snapshot = _accepted_base_query_snapshot( control_root, plan_id, task_id, expected_head, expected_tree, + str(proposal.get("handoff_id") or ""), + str(proposal.get("review_id") or ""), + final_head, + final_tree, ) if snapshot["recoverable_base_handoff_ids"]: raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") - for field in ("queried_historical_revision", "handoff_stores", "handoff_index"): - if receipt.get(field) != snapshot[field]: - raise SystemExit("accepted-result recovery receipt is stale") - return receipt + if receipt.get("expected_base_query") != snapshot["expected_base_query"]: + raise SystemExit("accepted-result recovery receipt expected-base query is stale") + if snapshot["proposal_state"] in {"mismatch", "ambiguous"}: + raise SystemExit("accepted-result recovery proposed result is mismatched or ambiguous") + return {**receipt, "proposal_state": snapshot["proposal_state"]} def _recovered_accepted_dependency_paths( @@ -1820,6 +1887,7 @@ def _recovered_accepted_dependency_paths( "task_id", "execution_baseline_recovery", "recovered_result", + "accepted_result_delta", "integrated_base", "integrated_head", } @@ -1830,6 +1898,12 @@ def _recovered_accepted_dependency_paths( "baseline_tree", "recovery_receipt", } + delta_fields = { + "expected_base_head", + "expected_base_tree", + "final_head", + "final_tree", + } dependencies = {str(value) for value in _as_list(task.get("depends_on"))} plan_id = str(task.get("plan_id") or "") workspace = task.get("workspace") if isinstance(task.get("workspace"), dict) else {} @@ -1865,14 +1939,34 @@ def _recovered_accepted_dependency_paths( or recovery.get("baseline_tree") != baseline.get("tree") ): raise SystemExit("execution_baseline_recovery binding or original baseline is mismatched") - validate_accepted_base_absence_receipt( + receipt = validate_accepted_base_absence_receipt( control_root, plan_id, dependency_id, recovery.get("recovery_receipt"), ) + if receipt.get("proposal_state") != "published": + raise SystemExit("accepted-result recovery proposed result is not published and indexed") + + delta = descriptor["accepted_result_delta"] + if not isinstance(delta, Mapping) or set(delta) != delta_fields: + raise SystemExit("accepted_result_delta must use the closed runtime identity shape") + proposal = receipt["proposed_recovered_result"] + if ( + delta.get("expected_base_head") != receipt.get("expected_base_head") + or delta.get("expected_base_tree") != receipt.get("expected_base_tree") + or delta.get("final_head") != proposal.get("final_head") + or delta.get("final_tree") != proposal.get("final_tree") + ): + raise SystemExit("accepted_result_delta is mismatched with the recovery receipt") + recovered_reference = descriptor["recovered_result"] + if ( + not isinstance(recovered_reference, Mapping) + or recovered_reference.get("handoff_id") != proposal.get("handoff_id") + ): + raise SystemExit("recovered result is mismatched with the recovery receipt proposal") - _, recovered = _exact_handoff_reference(handoff_root, descriptor["recovered_result"]) + _, recovered = _exact_handoff_reference(handoff_root, recovered_reference) related = recovered.get("related") if isinstance(recovered.get("related"), Mapping) else {} result = recovered.get("result") if isinstance(recovered.get("result"), Mapping) else {} review = ( @@ -1898,6 +1992,7 @@ def _recovered_accepted_dependency_paths( or reviewer.get("capability") != "judgment" or identity.get("artifact_id") != dependency_id or review.get("reviewed_head") != identity.get("revision") + or review.get("review_id") != proposal.get("review_id") ): raise SystemExit( "recovered result requires a fresh complete independent whole-task initial authority review with empty unavailable_evidence" @@ -1919,12 +2014,13 @@ def _recovered_accepted_dependency_paths( if reviewer.get("agent_id") == owner.get("agent_id"): raise SystemExit("recovered result reviewer is not independent from the task owner") - source_base = _resolve_commit(execution_root, str(baseline.get("head") or "")) - source_head = _resolve_commit(execution_root, str(identity.get("revision") or "")) + source_base = _resolve_commit(execution_root, str(delta.get("expected_base_head") or "")) + source_head = _resolve_commit(execution_root, str(delta.get("final_head") or "")) source_tree = _git(execution_root, "rev-parse", f"{source_head}^{{tree}}").strip() - if baseline.get("tree") != _git(execution_root, "rev-parse", f"{source_base}^{{tree}}").strip(): - raise SystemExit("execution_baseline_recovery baseline Git identity is mismatched") - if identity.get("source_tree") != source_tree: + source_base_tree = _git(execution_root, "rev-parse", f"{source_base}^{{tree}}").strip() + if delta.get("expected_base_tree") != source_base_tree: + raise SystemExit("accepted_result_delta expected base Git identity is mismatched") + if delta.get("final_tree") != source_tree or identity.get("source_tree") != source_tree: raise SystemExit("recovered result Git identity is mismatched") if subprocess.run( ["git", "-C", str(execution_root), "merge-base", "--is-ancestor", source_base, source_head], @@ -2169,7 +2265,8 @@ def _accepted_dependency_paths( "task_id", "accepted_result_base", "review_chain", "integrated_base", "integrated_head" } recovery_fields = { - "task_id", "execution_baseline_recovery", "recovered_result", "integrated_base", "integrated_head" + "task_id", "execution_baseline_recovery", "recovered_result", "accepted_result_delta", + "integrated_base", "integrated_head" } cumulative = [ item for item in descriptors if isinstance(item, Mapping) and set(item) == cumulative_fields @@ -3636,6 +3733,10 @@ def cmd_create_accepted_base_absence_receipt(args: argparse.Namespace) -> None: str(args.task_id), str(args.expected_head), str(args.expected_tree), + str(args.proposed_handoff_id), + str(args.proposed_review_id), + str(args.final_head), + str(args.final_tree), ) print(json.dumps(reference, sort_keys=True)) diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 75bc446..36540d2 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -1022,6 +1022,12 @@ def test_authority_recovery_receipt_is_helper_created_fresh_and_rechecked( ) binding_digest = hashlib.sha256(binding_path.read_bytes()).hexdigest() + dependency.write_text("missing accepted result base\n", encoding="utf-8") + git(root, "add", dependency_path) + git(root, "commit", "-qm", "missing accepted result base") + expected_base_head = git(root, "rev-parse", "HEAD") + expected_base_tree = git(root, "rev-parse", "HEAD^{tree}") + dependency.write_text("fresh whole-task accepted result\n", encoding="utf-8") git(root, "add", dependency_path) git(root, "commit", "-qm", "fresh recovered dependency result") @@ -1051,13 +1057,13 @@ def reviewer(agent_id: str) -> dict[str, object]: "required": True, "reviewer_independent": True, "verdict": "accept", - "reviewed_head": baseline, + "reviewed_head": expected_base_head, "review_id": "review-dependency-accepted-historical", "review_mode": "initial", "review_target_kind": "task", "repair_frontier": None, "review_reset": None, - "target_identity": identity(baseline, baseline_tree), + "target_identity": identity(expected_base_head, expected_base_tree), "reviewer": reviewer("historical-reviewer"), "evidence": { "mode": "direct", @@ -1107,25 +1113,8 @@ def reviewer(agent_id: str) -> dict[str, object]: handoff_dir = root / ".work-bundle/orchestration/handoff/executor/active" handoff_dir.mkdir(parents=True, exist_ok=True) recovered_path = handoff_dir / "handoff-recovered-dependency.yaml" - recovered_path.write_text( - ("\n".join(execution_context._dump_yaml(recovered_handoff)) + "\n").replace( - ": none\n", ': "none"\n' - ), - encoding="utf-8", - ) index = root / ".work-bundle/orchestration/handoff/index.jsonl" - index.write_text( - json.dumps({ - "id": recovered_handoff["id"], - "type": "executor-result", - "status": "active", - "path": str(recovered_path.relative_to(root)), - "related_plan": "plan-001", - "related_task": "task-dependency", - }) + "\n", - encoding="utf-8", - ) - + index.write_text("", encoding="utf-8") create_receipt = subprocess.run( [ sys.executable, @@ -1138,15 +1127,41 @@ def reviewer(agent_id: str) -> dict[str, object]: "--task-id", "task-dependency", "--expected-head", - baseline, + expected_base_head, "--expected-tree", - baseline_tree, + expected_base_tree, + "--proposed-handoff-id", + recovered_handoff["id"], + "--proposed-review-id", + recovered_review["review_id"], + "--final-head", + recovered_head, + "--final-tree", + recovered_tree, ], capture_output=True, text=True, ) assert create_receipt.returncode == 0, create_receipt.stderr receipt_reference = json.loads(create_receipt.stdout) + recovered_path.write_text( + ("\n".join(execution_context._dump_yaml(recovered_handoff)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + index.write_text( + json.dumps({ + "id": recovered_handoff["id"], + "type": "executor-result", + "status": "active", + "path": str(recovered_path.relative_to(root)), + "related_plan": "plan-001", + "related_task": "task-dependency", + }) + "\n", + encoding="utf-8", + ) + assert set(receipt_reference) == {"receipt_id", "receipt_sha256"} receipt_path = execution_context._recovery_receipt_path( root, "plan-001", "task-dependency", receipt_reference["receipt_id"] @@ -1157,9 +1172,14 @@ def reviewer(agent_id: str) -> dict[str, object]: assert receipt["binding_sha256"] == binding_digest assert receipt["baseline_head"] == baseline assert receipt["baseline_tree"] == baseline_tree - assert receipt["absence_result"] == "accepted_base_absent" + assert receipt["expected_base_query"]["result"] == "absent" assert receipt["freshness"] == "current_validation_attempt" - assert set(receipt["handoff_stores"]) == {"active", "archived"} + assert receipt["proposed_recovered_result"] == { + "handoff_id": recovered_handoff["id"], + "review_id": recovered_review["review_id"], + "final_head": recovered_head, + "final_tree": recovered_tree, + } recovered_reference = { "handoff_id": recovered_handoff["id"], @@ -1175,7 +1195,13 @@ def reviewer(agent_id: str) -> dict[str, object]: "recovery_receipt": receipt_reference, }, "recovered_result": recovered_reference, - "integrated_base": baseline, + "accepted_result_delta": { + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, + "final_head": recovered_head, + "final_tree": recovered_tree, + }, + "integrated_base": expected_base_head, "integrated_head": recovered_head, } dependent["depends_on"] = ["task-dependency"] @@ -1183,6 +1209,13 @@ def reviewer(agent_id: str) -> dict[str, object]: dependency_path } + wrong_delta_base = deepcopy(descriptor) + wrong_delta_base["accepted_result_delta"]["expected_base_head"] = baseline + wrong_delta_base["accepted_result_delta"]["expected_base_tree"] = baseline_tree + wrong_delta_base["integrated_base"] = baseline + with pytest.raises(SystemExit, match="accepted_result_delta.*mismatch"): + execution_context._accepted_dependency_paths(dependent, root, [wrong_delta_base]) + caller_assertion = deepcopy(descriptor) caller_assertion["execution_baseline_recovery"]["accepted_base_absent"] = True with pytest.raises(SystemExit, match="closed|shape"): @@ -1198,16 +1231,9 @@ def reviewer(agent_id: str) -> dict[str, object]: ), encoding="utf-8", ) - gap_receipt = execution_context.create_accepted_base_absence_receipt( - root, "plan-001", "task-dependency", baseline, baseline_tree - ) with pytest.raises(SystemExit, match="unavailable_evidence|complete"): execution_context._accepted_dependency_paths(dependent, root, [{ **descriptor, - "execution_baseline_recovery": { - **descriptor["execution_baseline_recovery"], - "recovery_receipt": gap_receipt, - }, "recovered_result": { **recovered_reference, "handoff_sha256": hashlib.sha256(recovered_path.read_bytes()).hexdigest(), @@ -1220,14 +1246,60 @@ def reviewer(agent_id: str) -> dict[str, object]: encoding="utf-8", ) - extra = handoff_dir / "unrelated-target-record.yaml" + extra = handoff_dir / "unrelated-record.yaml" extra.write_text( - "id: unrelated-target-record\ntype: executor-result\n" - "related: {plan: plan-001, task: task-dependency}\nresult: {state: partial}\n", + "id: unrelated-record\ntype: executor-result\n" + "related: {plan: plan-other, task: task-other}\nresult: {state: partial}\n", encoding="utf-8", ) - with pytest.raises(SystemExit, match="stale"): + index.write_text( + index.read_text(encoding="utf-8") + + json.dumps({"id": "unrelated-record", "related_plan": "plan-other", "related_task": "task-other"}) + + "\n", + encoding="utf-8", + ) + assert execution_context._accepted_dependency_paths(dependent, root, [descriptor]) == { + dependency_path + } + + archived_dir = root / ".work-bundle/orchestration/handoff/executor/archived" + archived_dir.mkdir(parents=True, exist_ok=True) + expected_base_handoff = deepcopy(recovered_handoff) + expected_base_handoff["id"] = "handoff-restored-accepted-base" + expected_base_handoff["acceptance_review"] = previous + expected_base_path = archived_dir / "handoff-restored-accepted-base.yaml" + expected_base_path.write_text( + ("\n".join(execution_context._dump_yaml(expected_base_handoff)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + with pytest.raises(SystemExit, match="recoverable accepted base"): execution_context._accepted_dependency_paths(dependent, root, [descriptor]) + expected_base_path.unlink() + + duplicate_path = archived_dir / "handoff-recovered-dependency-duplicate.yaml" + duplicate_path.write_bytes(recovered_path.read_bytes()) + with pytest.raises(SystemExit, match="ambiguous"): + execution_context._accepted_dependency_paths(dependent, root, [descriptor]) + duplicate_path.unlink() + + mismatched = deepcopy(recovered_handoff) + mismatched["acceptance_review"]["review_id"] = "different-proposed-review" + recovered_path.write_text( + ("\n".join(execution_context._dump_yaml(mismatched)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + with pytest.raises(SystemExit, match="proposal|proposed|mismatch"): + execution_context._accepted_dependency_paths(dependent, root, [{ + **descriptor, + "recovered_result": { + **recovered_reference, + "handoff_sha256": hashlib.sha256(recovered_path.read_bytes()).hexdigest(), + }, + }]) def test_authority_recovery_receipt_rejects_recoverable_accepted_base(tmp_path: Path) -> None: @@ -1296,7 +1368,15 @@ def test_authority_recovery_receipt_rejects_recoverable_accepted_base(tmp_path: with pytest.raises(SystemExit, match="recoverable accepted base"): execution_context.create_accepted_base_absence_receipt( - root, "plan-001", "task-dependency", commit, tree + root, + "plan-001", + "task-dependency", + commit, + tree, + "handoff-proposed-recovery", + "review-proposed-recovery", + commit, + tree, ) distinct = root / "tests/distinct-expected-base.txt" @@ -1307,7 +1387,15 @@ def test_authority_recovery_receipt_rejects_recoverable_accepted_base(tmp_path: distinct_head = git(root, "rev-parse", "HEAD") distinct_tree = git(root, "rev-parse", "HEAD^{tree}") reference = execution_context.create_accepted_base_absence_receipt( - root, "plan-001", "task-dependency", distinct_head, distinct_tree + root, + "plan-001", + "task-dependency", + distinct_head, + distinct_tree, + "handoff-proposed-recovery", + "review-proposed-recovery", + distinct_head, + distinct_tree, ) receipt_path = execution_context._recovery_receipt_path( root, "plan-001", "task-dependency", reference["receipt_id"] From 1e680770a83cf56dde98116711f7dc5acc7177bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 10:24:47 +0800 Subject: [PATCH 25/36] fix(orchestration): adopt published recovery results --- scripts/orchestration/dispatcher.py | 17 + scripts/orchestration/execution_context.py | 460 +++++++++++++++++++-- tests/test_wor108_context_projection.py | 246 +++++++++++ 3 files changed, 690 insertions(+), 33 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 5488d9b..3d10055 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -10,6 +10,7 @@ from execution_context import ( cmd_build_review_package, cmd_build_task_brief, + cmd_adopt_existing_recovered_result, cmd_create_accepted_base_absence_receipt, cmd_observe_task_validation, cmd_validate_executor_result, @@ -24,6 +25,7 @@ "init", "doctor", "state", "next-action-candidates", "git-status", "repository-preflight", "build-task-brief", "build-review-package", "validate-executor-result", "observe-task-validation", "create-accepted-base-absence-receipt", + "adopt-existing-recovered-result", "related", "write-doc", "write-spec", "list-specs", "set-spec-status", "index-specs", "write-plan", "list-plans", "set-plan-status", "archive-plan", "index-plans", "write-phase", "write-task", @@ -102,6 +104,21 @@ def build_parser() -> argparse.ArgumentParser: create_recovery_receipt.add_argument("--final-head", required=True) create_recovery_receipt.add_argument("--final-tree", required=True) create_recovery_receipt.set_defaults(func=cmd_create_accepted_base_absence_receipt) + adopt_recovered_result = sub.add_parser( + "adopt-existing-recovered-result", parents=[parent] + ) + adopt_recovered_result.add_argument("--plan-id", required=True) + adopt_recovered_result.add_argument("--task-id", required=True) + adopt_recovered_result.add_argument("--expected-head", required=True) + adopt_recovered_result.add_argument("--expected-tree", required=True) + adopt_recovered_result.add_argument("--handoff-id", required=True) + adopt_recovered_result.add_argument("--handoff-sha256", required=True) + adopt_recovered_result.add_argument("--review-id", required=True) + adopt_recovered_result.add_argument("--final-head", required=True) + adopt_recovered_result.add_argument("--final-tree", required=True) + adopt_recovered_result.add_argument("--prior-receipt-id", required=True) + adopt_recovered_result.add_argument("--prior-receipt-sha256", required=True) + adopt_recovered_result.set_defaults(func=cmd_adopt_existing_recovered_result) related = sub.add_parser("related", parents=[parent]) related.add_argument("--id", required=True) related.set_defaults(func=cmd_related) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index ff5326e..cb3a37b 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1543,6 +1543,24 @@ def _review_without_history(review: Mapping[str, Any]) -> dict[str, Any]: "observed_at", "freshness", } +LEGACY_RECOVERY_RECEIPT_KEYS = { + "receipt_id", + "schema", + "plan_id", + "task_id", + "binding_id", + "binding_sha256", + "baseline_head", + "baseline_tree", + "expected_base_head", + "expected_base_tree", + "queried_historical_revision", + "handoff_stores", + "handoff_index", + "absence_result", + "observed_at", + "freshness", +} def _recovery_receipt_path( @@ -1721,6 +1739,176 @@ def _accepted_base_query_snapshot( } +def _legacy_recovery_global_snapshot( + control_root: Path, + plan_id: str, + task_id: str, + expected_base_head: str, + expected_base_tree: str, +) -> dict[str, Any]: + """Reconstruct the obsolete revision-11 global-digest snapshot.""" + + handoff_root = control_root / ".work-bundle/orchestration/handoff" + stores: dict[str, dict[str, Any]] = {} + for status in ("active", "archived"): + records: list[dict[str, str]] = [] + for path in sorted((handoff_root / "executor" / status).glob("*")): + if not path.is_file() or path.is_symlink(): + continue + try: + handoff, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} + if related.get("plan") != plan_id or related.get("task") != task_id: + continue + records.append( + { + "handoff_id": str(handoff.get("id") or ""), + "path": path.relative_to(control_root).as_posix(), + "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + } + ) + stores[status] = { + "store_id": f"executor/{status}", + "records": records, + "sha256": semantic_digest(records), + } + + index_path = handoff_root / "index.jsonl" + if not index_path.is_file() or index_path.is_symlink(): + raise SystemExit("accepted-result recovery requires the native handoff index") + index_entries: list[dict[str, Any]] = [] + for number, line in enumerate(index_path.read_text(encoding="utf-8").splitlines(), 1): + if not line.strip(): + continue + try: + entry = json.loads(line) + except json.JSONDecodeError as error: + raise SystemExit( + f"accepted-result recovery handoff index is invalid at line {number}" + ) from error + if not isinstance(entry, dict): + raise SystemExit(f"accepted-result recovery handoff index is invalid at line {number}") + if entry.get("related_plan") == plan_id and entry.get("related_task") == task_id: + index_entries.append(entry) + index_identity = { + "index_id": "handoff/index.jsonl", + "path": index_path.relative_to(control_root).as_posix(), + "sha256": hashlib.sha256(index_path.read_bytes()).hexdigest(), + "projected_entries_sha256": semantic_digest(index_entries), + } + historical_revision = semantic_digest( + { + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, + "handoff_stores": stores, + "handoff_index": index_identity, + } + ) + return { + "queried_historical_revision": historical_revision, + "handoff_stores": stores, + "handoff_index": index_identity, + } + + +def _valid_recovered_result( + handoff: Mapping[str, Any], plan_id: str, task_id: str +) -> bool: + related = handoff.get("related") if isinstance(handoff.get("related"), Mapping) else {} + result = handoff.get("result") if isinstance(handoff.get("result"), Mapping) else {} + review = ( + handoff.get("acceptance_review") + if isinstance(handoff.get("acceptance_review"), Mapping) + else {} + ) + reset = review.get("review_reset") if isinstance(review.get("review_reset"), Mapping) else {} + evidence = review.get("evidence") if isinstance(review.get("evidence"), Mapping) else {} + reviewer = review.get("reviewer") if isinstance(review.get("reviewer"), Mapping) else {} + identity = review.get("target_identity") if isinstance(review.get("target_identity"), Mapping) else {} + if ( + handoff.get("type") != "executor-result" + or related.get("plan") != plan_id + or related.get("task") != task_id + or result.get("state") != "completed" + or review.get("verdict") != "accept" + or review.get("review_mode") != "initial" + or review.get("review_target_kind") != "task" + or review.get("reviewer_independent") is not True + or review.get("repair_frontier") is not None + or reset.get("reason_class") != "authority" + or evidence.get("unavailable_evidence") != [] + or reviewer.get("capability") != "judgment" + or identity.get("artifact_id") != task_id + or review.get("reviewed_head") != identity.get("revision") + ): + return False + try: + from review_runtime import ReviewContractError, validate_task_acceptance_review + + validate_task_acceptance_review(review) + owner = normalize_subagent_provenance( + handoff.get("delegation_evidence") + if isinstance(handoff.get("delegation_evidence"), Mapping) + else None + ) + except (ReviewContractError, OwnershipBlocker, KeyError, TypeError, ValueError): + return False + return reviewer.get("agent_id") != owner.get("agent_id") + + +def _write_query_scoped_recovery_receipt( + control_root: Path, + plan_id: str, + task_id: str, + binding: Mapping[str, Any], + binding_path: Path, + expected_base_head: str, + expected_base_tree: str, + proposed_handoff_id: str, + proposed_review_id: str, + final_head: str, + final_tree: str, + expected_base_query: Mapping[str, Any], +) -> dict[str, str]: + baseline = binding.get("baseline") if isinstance(binding.get("baseline"), Mapping) else {} + observed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + receipt_id = ( + f"accepted-result-recovery-{task_id}-" + f"{str(expected_base_query['sha256'])[:12]}-" + f"{hashlib.sha256(observed_at.encode()).hexdigest()[:12]}" + ) + receipt = { + "receipt_id": receipt_id, + "schema": RECOVERY_RECEIPT_SCHEMA, + "plan_id": plan_id, + "task_id": task_id, + "binding_id": str((binding.get("ownership") or {}).get("binding_id") or ""), + "binding_sha256": hashlib.sha256(binding_path.read_bytes()).hexdigest(), + "baseline_head": str(baseline.get("head") or ""), + "baseline_tree": str(baseline.get("tree") or ""), + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, + "expected_base_query": dict(expected_base_query), + "proposed_recovered_result": { + "handoff_id": proposed_handoff_id, + "review_id": proposed_review_id, + "final_head": final_head, + "final_tree": final_tree, + }, + "observed_at": observed_at, + "freshness": "current_validation_attempt", + } + path = _recovery_receipt_path(control_root, plan_id, task_id, receipt_id) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return { + "receipt_id": receipt_id, + "receipt_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + } + + def create_accepted_base_absence_receipt( control_root: Path, plan_id: str, @@ -1770,40 +1958,225 @@ def create_accepted_base_absence_receipt( raise SystemExit("accepted-result recovery rejected: recoverable accepted base handoff exists") if snapshot["proposal_state"] != "absent": raise SystemExit("accepted-result recovery rejected: proposed result already exists or is ambiguous") - observed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") - receipt_id = ( - f"accepted-result-recovery-{task_id}-" - f"{snapshot['expected_base_query']['sha256'][:12]}-" - f"{hashlib.sha256(observed_at.encode()).hexdigest()[:12]}" + return _write_query_scoped_recovery_receipt( + control_root, + plan_id, + task_id, + binding, + binding_path, + resolved_expected_head, + resolved_expected_tree, + proposed_handoff_id, + proposed_review_id, + resolved_final_head, + resolved_final_tree, + snapshot["expected_base_query"], + ) + + +def adopt_existing_recovered_result( + control_root: Path, + plan_id: str, + task_id: str, + expected_base_head: str, + expected_base_tree: str, + handoff_id: str, + handoff_sha256: str, + review_id: str, + final_head: str, + final_tree: str, + prior_receipt_reference: object, +) -> dict[str, str]: + """Adopt one already-published result whose rev11 receipt only globally staled.""" + + control_root = control_root.expanduser().resolve() + if not isinstance(prior_receipt_reference, Mapping) or set(prior_receipt_reference) != { + "receipt_id", "receipt_sha256" + }: + raise SystemExit("post-publication adoption prior receipt reference is invalid") + if not SAFE_ID_RE.fullmatch(handoff_id) or not SAFE_ID_RE.fullmatch(review_id): + raise SystemExit("post-publication adoption proposed identity is unsafe") + + binding_path = _binding_path(control_root, plan_id, task_id) + binding = load_task_execution_binding(control_root, plan_id, task_id) + baseline = binding.get("baseline") if isinstance(binding.get("baseline"), Mapping) else {} + binding_id = str((binding.get("ownership") or {}).get("binding_id") or "") + binding_sha256 = hashlib.sha256(binding_path.read_bytes()).hexdigest() + execution_root = Path(str(binding.get("execution_path") or "")).resolve() + resolved_expected_head = _resolve_commit(execution_root, expected_base_head) + resolved_expected_tree = _git( + execution_root, "rev-parse", f"{resolved_expected_head}^{{tree}}" + ).strip() + if resolved_expected_tree != expected_base_tree: + raise SystemExit("post-publication adoption expected base Git identity is mismatched") + resolved_final_head = _resolve_commit(execution_root, final_head) + resolved_final_tree = _git( + execution_root, "rev-parse", f"{resolved_final_head}^{{tree}}" + ).strip() + if resolved_final_tree != final_tree: + raise SystemExit("post-publication adoption final Git identity is mismatched") + + prior_receipt_id = str(prior_receipt_reference["receipt_id"]) + prior_path = _recovery_receipt_path(control_root, plan_id, task_id, prior_receipt_id) + if not prior_path.is_file() or prior_path.is_symlink(): + raise SystemExit("post-publication adoption prior receipt is missing") + if hashlib.sha256(prior_path.read_bytes()).hexdigest() != str( + prior_receipt_reference["receipt_sha256"] + ): + raise SystemExit("post-publication adoption prior receipt digest is stale") + try: + prior = json.loads(prior_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + raise SystemExit("post-publication adoption prior receipt is invalid") from error + if not isinstance(prior, dict) or set(prior) != LEGACY_RECOVERY_RECEIPT_KEYS: + raise SystemExit("post-publication adoption requires the closed revision-11 receipt schema") + if ( + prior.get("receipt_id") != prior_receipt_id + or prior.get("schema") != RECOVERY_RECEIPT_SCHEMA + or prior.get("plan_id") != plan_id + or prior.get("task_id") != task_id + or prior.get("binding_id") != binding_id + or prior.get("binding_sha256") != binding_sha256 + or prior.get("baseline_head") != baseline.get("head") + or prior.get("baseline_tree") != baseline.get("tree") + or prior.get("expected_base_head") != resolved_expected_head + or prior.get("expected_base_tree") != resolved_expected_tree + or prior.get("absence_result") != "accepted_base_absent" + or prior.get("freshness") != "current_validation_attempt" + or not isinstance(prior.get("observed_at"), str) + ): + raise SystemExit("post-publication adoption prior receipt has a non-global defect") + + stores = prior.get("handoff_stores") + index_identity = prior.get("handoff_index") + if not isinstance(stores, Mapping) or set(stores) != {"active", "archived"}: + raise SystemExit("post-publication adoption prior receipt stores are invalid") + recorded_ids: set[str] = set() + for status in ("active", "archived"): + store = stores[status] + if not isinstance(store, Mapping) or set(store) != {"store_id", "records", "sha256"}: + raise SystemExit("post-publication adoption prior receipt store is invalid") + records = store.get("records") + if ( + store.get("store_id") != f"executor/{status}" + or not isinstance(records, list) + or store.get("sha256") != semantic_digest(records) + ): + raise SystemExit("post-publication adoption prior receipt store digest is invalid") + for record in records: + if not isinstance(record, Mapping) or set(record) != {"handoff_id", "path", "sha256"}: + raise SystemExit("post-publication adoption prior receipt record is invalid") + recorded_ids.add(str(record.get("handoff_id") or "")) + record_path = (control_root / str(record.get("path") or "")).resolve() + try: + record_path.relative_to(control_root) + except ValueError as error: + raise SystemExit("post-publication adoption prior receipt record path is unsafe") from error + if ( + not record_path.is_file() + or record_path.is_symlink() + or hashlib.sha256(record_path.read_bytes()).hexdigest() != record.get("sha256") + ): + raise SystemExit("post-publication adoption prior receipt record is stale") + try: + recorded_handoff, _ = _read_structured(record_path) + except (OSError, SystemExit, ValueError) as error: + raise SystemExit("post-publication adoption prior receipt record is invalid") from error + if _is_recoverable_accepted_base( + recorded_handoff, + plan_id, + task_id, + resolved_expected_head, + resolved_expected_tree, + ): + raise SystemExit("post-publication adoption prior expected-base query was not absent") + if handoff_id in recorded_ids: + raise SystemExit("post-publication adoption candidate was already present in the prior receipt") + if not isinstance(index_identity, Mapping) or set(index_identity) != { + "index_id", "path", "sha256", "projected_entries_sha256" + }: + raise SystemExit("post-publication adoption prior receipt index is invalid") + if ( + index_identity.get("index_id") != "handoff/index.jsonl" + or index_identity.get("path") != ".work-bundle/orchestration/handoff/index.jsonl" + or any( + not re.fullmatch(r"[0-9a-f]{64}", str(index_identity.get(field) or "")) + for field in ("sha256", "projected_entries_sha256") + ) + ): + raise SystemExit("post-publication adoption prior receipt index digest is invalid") + prior_revision = semantic_digest( + { + "expected_base_head": resolved_expected_head, + "expected_base_tree": resolved_expected_tree, + "handoff_stores": stores, + "handoff_index": index_identity, + } + ) + if prior.get("queried_historical_revision") != prior_revision: + raise SystemExit("post-publication adoption prior receipt historical revision is invalid") + expected_receipt_prefix = f"accepted-result-recovery-{task_id}-{prior_revision[:12]}-" + observed_suffix = hashlib.sha256(str(prior["observed_at"]).encode()).hexdigest()[:12] + if prior_receipt_id != f"{expected_receipt_prefix}{observed_suffix}": + raise SystemExit("post-publication adoption prior receipt identity is invalid") + + snapshot = _accepted_base_query_snapshot( + control_root, + plan_id, + task_id, + resolved_expected_head, + resolved_expected_tree, + handoff_id, + review_id, + resolved_final_head, + resolved_final_tree, + ) + if snapshot["recoverable_base_handoff_ids"]: + raise SystemExit("post-publication adoption rejected: recoverable accepted base exists") + if snapshot["proposal_state"] != "published": + raise SystemExit("post-publication adoption candidate is missing, mismatched, or ambiguous") + + handoff_root = control_root / ".work-bundle/orchestration/handoff" + _, candidate = _exact_handoff_reference( + handoff_root, {"handoff_id": handoff_id, "handoff_sha256": handoff_sha256} + ) + if not _valid_recovered_result(candidate, plan_id, task_id): + raise SystemExit("post-publication adoption candidate is not a valid recovered result") + for path in sorted(handoff_root.glob("executor/*/*")): + if not path.is_file() or path.is_symlink(): + continue + if hashlib.sha256(path.read_bytes()).hexdigest() == handoff_sha256: + continue + try: + other, _ = _read_structured(path) + except (OSError, SystemExit, ValueError): + continue + if _valid_recovered_result(other, plan_id, task_id): + raise SystemExit("post-publication adoption rejected: valid competing recovered result exists") + + current_legacy = _legacy_recovery_global_snapshot( + control_root, + plan_id, + task_id, + resolved_expected_head, + resolved_expected_tree, + ) + if all(prior.get(field) == current_legacy[field] for field in current_legacy): + raise SystemExit("post-publication adoption prior receipt is not globally stale") + return _write_query_scoped_recovery_receipt( + control_root, + plan_id, + task_id, + binding, + binding_path, + resolved_expected_head, + resolved_expected_tree, + handoff_id, + review_id, + resolved_final_head, + resolved_final_tree, + snapshot["expected_base_query"], ) - receipt = { - "receipt_id": receipt_id, - "schema": RECOVERY_RECEIPT_SCHEMA, - "plan_id": plan_id, - "task_id": task_id, - "binding_id": str((binding.get("ownership") or {}).get("binding_id") or ""), - "binding_sha256": hashlib.sha256(binding_path.read_bytes()).hexdigest(), - "baseline_head": baseline_head, - "baseline_tree": baseline_tree, - "expected_base_head": resolved_expected_head, - "expected_base_tree": resolved_expected_tree, - "expected_base_query": snapshot["expected_base_query"], - "proposed_recovered_result": { - "handoff_id": proposed_handoff_id, - "review_id": proposed_review_id, - "final_head": resolved_final_head, - "final_tree": resolved_final_tree, - }, - "observed_at": observed_at, - "freshness": "current_validation_attempt", - } - path = _recovery_receipt_path(control_root, plan_id, task_id, receipt_id) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8") - return { - "receipt_id": receipt_id, - "receipt_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), - } def validate_accepted_base_absence_receipt( @@ -3741,6 +4114,27 @@ def cmd_create_accepted_base_absence_receipt(args: argparse.Namespace) -> None: print(json.dumps(reference, sort_keys=True)) +def cmd_adopt_existing_recovered_result(args: argparse.Namespace) -> None: + root = resolve_workspace_root(args) + reference = adopt_existing_recovered_result( + root, + str(args.plan_id), + str(args.task_id), + str(args.expected_head), + str(args.expected_tree), + str(args.handoff_id), + str(args.handoff_sha256), + str(args.review_id), + str(args.final_head), + str(args.final_tree), + { + "receipt_id": str(args.prior_receipt_id), + "receipt_sha256": str(args.prior_receipt_sha256), + }, + ) + print(json.dumps(reference, sort_keys=True)) + + def _observation_kwargs(args: argparse.Namespace) -> dict[str, Any]: return { "workspace_id": getattr(args, "workspace_id", None) or None, diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 36540d2..874d791 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -1209,6 +1209,183 @@ def reviewer(agent_id: str) -> dict[str, object]: dependency_path } + empty_records: list[dict[str, str]] = [] + legacy_stores = { + status: { + "store_id": f"executor/{status}", + "records": empty_records, + "sha256": execution_context.semantic_digest(empty_records), + } + for status in ("active", "archived") + } + legacy_index = { + "index_id": "handoff/index.jsonl", + "path": ".work-bundle/orchestration/handoff/index.jsonl", + "sha256": hashlib.sha256(b"").hexdigest(), + "projected_entries_sha256": execution_context.semantic_digest([]), + } + legacy_revision = execution_context.semantic_digest({ + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, + "handoff_stores": legacy_stores, + "handoff_index": legacy_index, + }) + legacy_observed_at = "2026-09-06T01:01:00Z" + legacy_receipt_id = ( + f"accepted-result-recovery-task-dependency-{legacy_revision[:12]}-" + f"{hashlib.sha256(legacy_observed_at.encode()).hexdigest()[:12]}" + ) + legacy_receipt = { + "receipt_id": legacy_receipt_id, + "schema": "accepted-result-recovery-receipt-v1", + "plan_id": "plan-001", + "task_id": "task-dependency", + "binding_id": binding["ownership"]["binding_id"], + "binding_sha256": binding_digest, + "baseline_head": baseline, + "baseline_tree": baseline_tree, + "expected_base_head": expected_base_head, + "expected_base_tree": expected_base_tree, + "queried_historical_revision": legacy_revision, + "handoff_stores": legacy_stores, + "handoff_index": legacy_index, + "absence_result": "accepted_base_absent", + "observed_at": legacy_observed_at, + "freshness": "current_validation_attempt", + } + legacy_receipt_path = execution_context._recovery_receipt_path( + root, "plan-001", "task-dependency", legacy_receipt_id + ) + legacy_receipt_path.write_text( + json.dumps(legacy_receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + legacy_reference = { + "receipt_id": legacy_receipt_id, + "receipt_sha256": hashlib.sha256(legacy_receipt_path.read_bytes()).hexdigest(), + } + handoff_bytes = recovered_path.read_bytes() + index_bytes = index.read_bytes() + adopted_reference = execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + recovered_reference["handoff_sha256"], + recovered_review["review_id"], + recovered_head, + recovered_tree, + legacy_reference, + ) + assert recovered_path.read_bytes() == handoff_bytes + assert index.read_bytes() == index_bytes + adopted_descriptor = deepcopy(descriptor) + adopted_descriptor["execution_baseline_recovery"]["recovery_receipt"] = adopted_reference + assert execution_context._accepted_dependency_paths( + dependent, root, [adopted_descriptor] + ) == {dependency_path} + + adopt_cli = subprocess.run( + [ + sys.executable, + str(REPO_ROOT / "scripts/orch.py"), + "adopt-existing-recovered-result", + "--project-root", + str(root), + "--plan-id", + "plan-001", + "--task-id", + "task-dependency", + "--expected-head", + expected_base_head, + "--expected-tree", + expected_base_tree, + "--handoff-id", + recovered_handoff["id"], + "--handoff-sha256", + recovered_reference["handoff_sha256"], + "--review-id", + recovered_review["review_id"], + "--final-head", + recovered_head, + "--final-tree", + recovered_tree, + "--prior-receipt-id", + legacy_reference["receipt_id"], + "--prior-receipt-sha256", + legacy_reference["receipt_sha256"], + ], + capture_output=True, + text=True, + ) + assert adopt_cli.returncode == 0, adopt_cli.stderr + assert set(json.loads(adopt_cli.stdout)) == {"receipt_id", "receipt_sha256"} + assert recovered_path.read_bytes() == handoff_bytes + assert index.read_bytes() == index_bytes + + with pytest.raises(SystemExit, match="prior receipt.*missing"): + execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + recovered_reference["handoff_sha256"], + recovered_review["review_id"], + recovered_head, + recovered_tree, + {"receipt_id": "missing-prior-receipt", "receipt_sha256": "0" * 64}, + ) + with pytest.raises(SystemExit, match="stale"): + execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + "0" * 64, + recovered_review["review_id"], + recovered_head, + recovered_tree, + legacy_reference, + ) + + invalid_legacy = deepcopy(legacy_receipt) + invalid_legacy["baseline_head"] = expected_base_head + invalid_observed_at = "2026-09-06T01:01:01Z" + invalid_legacy["observed_at"] = invalid_observed_at + invalid_legacy_id = ( + f"accepted-result-recovery-task-dependency-{legacy_revision[:12]}-" + f"{hashlib.sha256(invalid_observed_at.encode()).hexdigest()[:12]}" + ) + invalid_legacy["receipt_id"] = invalid_legacy_id + invalid_legacy_path = execution_context._recovery_receipt_path( + root, "plan-001", "task-dependency", invalid_legacy_id + ) + invalid_legacy_path.write_text( + json.dumps(invalid_legacy, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + with pytest.raises(SystemExit, match="prior receipt|baseline|non-global"): + execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + recovered_reference["handoff_sha256"], + recovered_review["review_id"], + recovered_head, + recovered_tree, + { + "receipt_id": invalid_legacy_id, + "receipt_sha256": hashlib.sha256(invalid_legacy_path.read_bytes()).hexdigest(), + }, + ) + wrong_delta_base = deepcopy(descriptor) wrong_delta_base["accepted_result_delta"]["expected_base_head"] = baseline wrong_delta_base["accepted_result_delta"]["expected_base_tree"] = baseline_tree @@ -1262,6 +1439,47 @@ def reviewer(agent_id: str) -> dict[str, object]: dependency_path } + competitor = deepcopy(recovered_handoff) + competitor["id"] = "handoff-competing-recovered-dependency" + competitor["acceptance_review"]["review_id"] = "review-competing-recovery" + competitor_path = handoff_dir / "handoff-competing-recovered-dependency.yaml" + competitor_path.write_text( + ("\n".join(execution_context._dump_yaml(competitor)) + "\n").replace( + ": none\n", ': "none"\n' + ), + encoding="utf-8", + ) + before_competitor_index = index.read_bytes() + index.write_text( + index.read_text(encoding="utf-8") + + json.dumps({ + "id": competitor["id"], + "type": "executor-result", + "status": "active", + "path": str(competitor_path.relative_to(root)), + "related_plan": "plan-001", + "related_task": "task-dependency", + }) + + "\n", + encoding="utf-8", + ) + with pytest.raises(SystemExit, match="competing recovered result"): + execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + recovered_reference["handoff_sha256"], + recovered_review["review_id"], + recovered_head, + recovered_tree, + legacy_reference, + ) + competitor_path.unlink() + index.write_bytes(before_competitor_index) + archived_dir = root / ".work-bundle/orchestration/handoff/executor/archived" archived_dir.mkdir(parents=True, exist_ok=True) expected_base_handoff = deepcopy(recovered_handoff) @@ -1276,12 +1494,40 @@ def reviewer(agent_id: str) -> dict[str, object]: ) with pytest.raises(SystemExit, match="recoverable accepted base"): execution_context._accepted_dependency_paths(dependent, root, [descriptor]) + with pytest.raises(SystemExit, match="recoverable accepted base"): + execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + recovered_reference["handoff_sha256"], + recovered_review["review_id"], + recovered_head, + recovered_tree, + legacy_reference, + ) expected_base_path.unlink() duplicate_path = archived_dir / "handoff-recovered-dependency-duplicate.yaml" duplicate_path.write_bytes(recovered_path.read_bytes()) with pytest.raises(SystemExit, match="ambiguous"): execution_context._accepted_dependency_paths(dependent, root, [descriptor]) + with pytest.raises(SystemExit, match="ambiguous"): + execution_context.adopt_existing_recovered_result( + root, + "plan-001", + "task-dependency", + expected_base_head, + expected_base_tree, + recovered_handoff["id"], + recovered_reference["handoff_sha256"], + recovered_review["review_id"], + recovered_head, + recovered_tree, + legacy_reference, + ) duplicate_path.unlink() mismatched = deepcopy(recovered_handoff) From 5a483de944c8eec02baef052f6a9ca3f9990cc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 10:37:10 +0800 Subject: [PATCH 26/36] fix(orchestration): reference oversized review diffs --- scripts/orchestration/execution_context.py | 33 ++++++++++++++++--- tests/test_orchestration_execution_context.py | 27 +++++++++++---- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index cb3a37b..7c9609b 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -3892,16 +3892,38 @@ def build_review_package(args: argparse.Namespace) -> Path: raise SystemExit("review-blocked: repair base does not match previous reviewed identity") if repair_frontier["repaired_identity"]["source_tree"] != head_tree: raise SystemExit("review-blocked: repair head does not match repaired identity") + omitted_diff_bytes = 0 if len(diff.encode("utf-8")) > MAX_DIFF_BYTES or diff.count("\n") > MAX_DIFF_LINES: oversized = ", ".join( sorted({path for line in name_status for path in _paths_from_name_status(line)}) ) or "unknown" - raise SystemExit( - "review-blocked: task-local review diff exceeds the bounded package limit " - f"(oversized paths: {oversized}). This is a compiler-or-plan defect, " - "not a reason to add implementation tasks." + if head.startswith("worktree:"): + raise SystemExit( + "review-blocked: oversized uncommitted task-local diff has no immutable " + f"source identity (oversized paths: {oversized})" + ) + omitted_diff_bytes = len(diff.encode("utf-8")) + base_tree = _git(execution_root, "rev-parse", f"{base}^{{tree}}").strip() + head_tree = _git(execution_root, "rev-parse", f"{head}^{{tree}}").strip() + diff_digest = hashlib.sha256(diff.encode("utf-8")).hexdigest() + changed_paths = sorted( + {path for line in name_status for path in _paths_from_name_status(line)} ) - diff = _redact_diff(diff) + diff = "\n".join( + [ + "Exact source diff reference (content omitted from this bounded packet).", + f"Base commit: {base}", + f"Base tree: {base_tree}", + f"Head commit: {head}", + f"Head tree: {head_tree}", + f"Task-local diff SHA-256: {diff_digest}", + "Changed paths:", + *[f"- {path}" for path in changed_paths], + "Review the exact Git diff between these commits, restricted to the changed paths above.", + ] + ) + else: + diff = _redact_diff(diff) changes = handoff.get("changes") if isinstance(handoff.get("changes"), dict) else {} handoff_files = [item for item in _as_list(changes.get("files")) if isinstance(item, dict)] @@ -4040,6 +4062,7 @@ def build_review_package(args: argparse.Namespace) -> Path: task, review_package=package, evidence_projection=evidence_projection, + omitted_by_reference_bytes=omitted_diff_bytes, expansion_reason=next( (item.get("expansion_reason") for item in evidence_projection if item.get("expansion_reason")), None, diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index aad29b3..9f6f9d4 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -1994,7 +1994,7 @@ def test_review_package_keeps_sibling_and_rename_paths_as_out_of_scope_diagnosti assert "No out-of-scope change is present" not in package -def test_review_package_overflow_fails_closed_on_write_scope_diff_only( +def test_review_package_projects_committed_oversized_diff_by_exact_source_identity( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: root, _, task = workspace(tmp_path) @@ -2010,14 +2010,27 @@ def test_review_package_overflow_fails_closed_on_write_scope_diff_only( base = git(root, "rev-parse", "HEAD") scoped.write_text("def compile_task():\n return 'IN-SCOPE-OVERFLOW-PAYLOAD'\n", encoding="utf-8") outsider.write_text("OUTSIDE = '" + ("Y" * 400) + "'\n", encoding="utf-8") + git(root, "add", ".") + git(root, "commit", "-qm", "oversized committed task result") + head = git(root, "rev-parse", "HEAD") + head_tree = git(root, "rev-parse", "HEAD^{tree}") handoff = _bind_passing_observation(root, task) - with pytest.raises(SystemExit, match="review-blocked|bounded package limit") as error: - build_review_package(args(root, task, handoff=str(handoff), base=base, head="worktree")) - - message = str(error.value) - assert WRITE_SCOPE_FILE in message - assert "implementation task" not in message.lower() or "not a reason to add implementation" in message + target = build_review_package( + args(root, task, handoff=str(handoff), base=base, head=head) + ) + package = target.read_text(encoding="utf-8") + metrics = json.loads( + target.with_name("review-package-metrics.json").read_text(encoding="utf-8") + )["compiled_context_metrics"] + + assert "Exact source diff reference" in package + assert f"Base commit: {base}" in package + assert f"Head commit: {head}" in package + assert f"Head tree: {head_tree}" in package + assert WRITE_SCOPE_FILE in package + assert "IN-SCOPE-OVERFLOW-PAYLOAD" not in package + assert metrics["omitted_by_reference_bytes"] > 0 def test_review_package_does_not_overflow_on_out_of_scope_payload( From ff6b73a3f64ee9198f34a1e35283ef102b1c258e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 11:31:42 +0800 Subject: [PATCH 27/36] chore(orchestration): converge accepted WOR-108 results --- evals/wor108/fixtures.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/evals/wor108/fixtures.json b/evals/wor108/fixtures.json index 22c8b07..354235a 100644 --- a/evals/wor108/fixtures.json +++ b/evals/wor108/fixtures.json @@ -106,7 +106,7 @@ "area": "subagent_ownership", "title": "Independent disjoint tasks fan out before any wait", "oracle": "pytest", - "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg05_independent_disjoint_tasks_dispatch_before_wait"], + "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg05_independent_disjoint_tasks_dispatch_before_wait_and_convergence"], "expected": "passed" }, { @@ -126,7 +126,10 @@ "area": "subagent_ownership", "title": "Repair mutation remains subagent-owned", "oracle": "pytest", - "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg07_repair_remains_subagent_owned"], + "pytest_nodes": [ + "tests/test_wor108_subagent_ownership.py::test_sg07_repair_retains_owner_binding_baseline_evidence_and_frontier", + "tests/test_wor108_subagent_ownership.py::test_sg07_repair_allows_only_an_authorized_replacement_owner" + ], "expected": "passed" }, { @@ -135,7 +138,7 @@ "title": "Ownership provenance is provider-neutral and rejects visibility-specific fields", "oracle": "pytest", "pytest_nodes": [ - "tests/test_wor108_subagent_ownership.py::test_sg08_ownership_is_mechanism_neutral", + "tests/test_wor108_subagent_ownership.py::test_sg08_native_mechanisms_preserve_task_binding_handoff_and_validation_semantics", "tests/test_wor108_subagent_ownership.py::test_visibility_specific_provenance_is_rejected" ], "expected": "passed" From ff96d979cea6cab444385af2972895b46e659b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 11:51:05 +0800 Subject: [PATCH 28/36] fix(evals): enforce migration impact completeness --- evals/wor108/migration-impact.json | 1 + evals/wor108/verify.py | 12 ++++++++++++ tests/test_wor108_closure.py | 15 ++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/evals/wor108/migration-impact.json b/evals/wor108/migration-impact.json index 136a615..8204bcc 100644 --- a/evals/wor108/migration-impact.json +++ b/evals/wor108/migration-impact.json @@ -25,6 +25,7 @@ "skills/wb-initialize-project/SKILL.md" ], "operations": [ + "scripts/orchestration/dispatcher.py", "scripts/orchestration/execution_context.py", "scripts/orchestration/review_runtime.py", "scripts/orchestration/task_ownership.py", diff --git a/evals/wor108/verify.py b/evals/wor108/verify.py index c3e7066..38f98cb 100644 --- a/evals/wor108/verify.py +++ b/evals/wor108/verify.py @@ -7,6 +7,7 @@ import json from pathlib import Path import re +import subprocess from typing import Any @@ -115,6 +116,17 @@ def _verify_migration(path: Path, fixture_ids: tuple[str, ...]) -> int: flattened = [surface for group in surfaces.values() for surface in group] if len(flattened) != len(set(flattened)) or any(not (REPO_ROOT / surface).is_file() for surface in flattened): raise VerificationError("changed surfaces must be unique existing files") + baseline = manifest["accepted_legacy_baseline"]["commit"] + changed = subprocess.run( + ["git", "-C", str(REPO_ROOT), "diff", "--name-only", baseline, "HEAD", "--"], + check=False, + capture_output=True, + text=True, + ) + if changed.returncode != 0: + raise VerificationError("accepted baseline delta is unavailable") + if set(flattened) != set(changed.stdout.splitlines()): + raise VerificationError("changed surface completeness mismatch") if tuple(manifest["evaluation_identities"]) != fixture_ids: raise VerificationError("migration evaluation identities mismatch") if manifest["handoff_constraints"] != { diff --git a/tests/test_wor108_closure.py b/tests/test_wor108_closure.py index 30e67e9..1752144 100644 --- a/tests/test_wor108_closure.py +++ b/tests/test_wor108_closure.py @@ -21,7 +21,7 @@ def test_wor108_closure_registry_has_exact_public_fixture_identities() -> None: assert result == { "evaluation_id": "wor108-legacy-closure-v1", "fixtures": 22, - "changed_surfaces": 39, + "changed_surfaces": 40, "verdict": "accepted", } @@ -71,3 +71,16 @@ def test_wor108_migration_impact_rejects_final_git_identity(tmp_path: Path) -> N with pytest.raises(wor108_verify.VerificationError, match="closed shape"): wor108_verify.verify(migration_path=tampered) + + +def test_wor108_migration_impact_rejects_incomplete_baseline_delta( + tmp_path: Path, +) -> None: + manifest = json.loads((EVAL_ROOT / "migration-impact.json").read_text(encoding="utf-8")) + operations = manifest["changed_surfaces"]["operations"] + operations.remove("scripts/orchestration/execution_context.py") + tampered = tmp_path / "migration-impact.json" + tampered.write_text(json.dumps(manifest), encoding="utf-8") + + with pytest.raises(wor108_verify.VerificationError, match="changed surface completeness"): + wor108_verify.verify(migration_path=tampered) From 59699f825637733bdd6e8996c7619e0fead88d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 12:21:16 +0800 Subject: [PATCH 29/36] fix(evals): bind exact WOR-108 production oracles --- evals/wor108/fixtures.json | 8 ++++---- tests/test_wor108_closure.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/evals/wor108/fixtures.json b/evals/wor108/fixtures.json index 354235a..d199448 100644 --- a/evals/wor108/fixtures.json +++ b/evals/wor108/fixtures.json @@ -7,7 +7,7 @@ "area": "repair_frontier", "title": "Initial and repair review modes are native closed contract values", "oracle": "pytest", - "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_01_initial_and_repair_modes_are_native_and_closed"], + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_01_h1_repairs_recorded_a_without_reacquiring_unrecorded_latent_b"], "expected": "passed" }, { @@ -23,7 +23,7 @@ "area": "repair_frontier", "title": "Repair reuses frozen evidence by reference without copying whole review history", "oracle": "pytest", - "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_03_repair_reuses_frozen_evidence_without_copying_history"], + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_03_capable_untouched_invariant_stays_blocking_during_narrow_repair"], "expected": "passed" }, { @@ -47,7 +47,7 @@ "area": "repair_frontier", "title": "Repair rejects stale or relabelled repaired identities", "oracle": "pytest", - "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_06_repair_rejects_stale_or_relabelled_identity"], + "pytest_nodes": ["tests/test_wor108_review_frontier.py::test_rf_06_final_broad_integrated_review_rediscovers_and_classifies_latent_b"], "expected": "passed" }, { @@ -98,7 +98,7 @@ "area": "subagent_ownership", "title": "Controller mutation of task write scope is rejected despite green validation", "oracle": "pytest", - "pytest_nodes": ["tests/test_wor108_subagent_ownership.py::test_sg04_controller_task_mutation_is_rejected_even_when_validation_passes"], + "pytest_nodes": ["tests/test_wor108_context_projection.py::test_completed_task_acceptance_rejects_controller_task_scope_mutation"], "expected": "passed" }, { diff --git a/tests/test_wor108_closure.py b/tests/test_wor108_closure.py index 1752144..1181038 100644 --- a/tests/test_wor108_closure.py +++ b/tests/test_wor108_closure.py @@ -26,6 +26,28 @@ def test_wor108_closure_registry_has_exact_public_fixture_identities() -> None: } +def test_wor108_closure_registry_binds_linear_exact_production_oracles() -> None: + package = json.loads((EVAL_ROOT / "fixtures.json").read_text(encoding="utf-8")) + nodes_by_id = { + fixture["fixture_id"]: fixture["pytest_nodes"] + for fixture in package["fixtures"] + } + assert nodes_by_id | { + "RF-01": [ + "tests/test_wor108_review_frontier.py::test_rf_01_h1_repairs_recorded_a_without_reacquiring_unrecorded_latent_b" + ], + "RF-03": [ + "tests/test_wor108_review_frontier.py::test_rf_03_capable_untouched_invariant_stays_blocking_during_narrow_repair" + ], + "RF-06": [ + "tests/test_wor108_review_frontier.py::test_rf_06_final_broad_integrated_review_rediscovers_and_classifies_latent_b" + ], + "SG-04": [ + "tests/test_wor108_context_projection.py::test_completed_task_acceptance_rejects_controller_task_scope_mutation" + ], + } == nodes_by_id + + def test_wor108_fixture_registry_rejects_missing_identity(tmp_path: Path) -> None: fixtures = json.loads((EVAL_ROOT / "fixtures.json").read_text(encoding="utf-8")) fixtures["fixtures"] = fixtures["fixtures"][:-1] From f221a6108c66bd0bb86ea7c049f20b4db84db8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 13:10:18 +0800 Subject: [PATCH 30/36] fix(orchestration): close task acceptance evidence gaps --- scripts/orchestration/execution_context.py | 11 +- tests/test_orchestration_execution_context.py | 176 +++++++++++++++++- tests/test_wor108_context_projection.py | 22 ++- 3 files changed, 199 insertions(+), 10 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 7c9609b..dc5961f 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -3227,7 +3227,11 @@ def validate_executor_result_for_task( ) task_ownership = None if state == "completed": - runtime_mutation_events = list(mutation_events or []) + if mutation_events is None: + raise SystemExit( + "review-blocked: completed task requires harness-owned mutation_events evidence" + ) + runtime_mutation_events = list(mutation_events) if any(not isinstance(event, Mapping) for event in runtime_mutation_events): raise SystemExit("Runtime mutation_events must contain mappings") fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} @@ -3390,7 +3394,8 @@ def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} repaired = fit.get("result") == "repaired" mode = review.get("review_mode") - if repaired and mode != "repair": + reset_initial = mode == "initial" and review.get("review_reset") is not None + if repaired and mode != "repair" and not reset_initial: raise SystemExit("Repaired task completion requires a sequenced repair acceptance_review") if mode is not None: try: @@ -4167,7 +4172,7 @@ def _observation_kwargs(args: argparse.Namespace) -> dict[str, Any]: "accepted_dependency_deltas": ( getattr(args, "accepted_dependency_deltas", None) or None ), - "mutation_events": getattr(args, "mutation_events", None) or None, + "mutation_events": getattr(args, "mutation_events", None), "prior_ownership": getattr(args, "prior_ownership", None) or None, "repair_continuity": getattr(args, "repair_continuity", None) or None, "authorized_replacements": getattr(args, "authorized_replacements", None) or None, diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 9f6f9d4..17f2ef7 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -296,6 +296,74 @@ def test_normal_cli_projects_controller_mutation_events_into_acceptance( assert task_data.get("status") != "Completed" +@pytest.mark.parametrize("command", ["validate-executor-result", "set-plan-status"]) +def test_completed_task_cli_rejects_missing_controller_mutation_evidence( + tmp_path: Path, command: str +) -> None: + root, task, _, handoff, _ = _counted_validation(tmp_path) + handoff_path = root / ".work-bundle/orchestration/handoff/executor/active/handoff-task-004.yaml" + handoff_path.write_text( + "\n".join(execution_context._dump_yaml(handoff)) + "\n", encoding="utf-8" + ) + if command == "validate-executor-result": + operation = [command, "--task", str(task), "--handoff", str(handoff_path)] + else: + operation = [ + command, + "--id", + "task-004", + "--kind", + "task", + "--plan-id", + "plan-001", + "--status", + "Completed", + "--handoff", + str(handoff_path), + ] + parsed = orchestration_dispatcher.build_parser().parse_args( + [*operation, "--project-root", str(root)] + ) + with pytest.raises(SystemExit, match="mutation.*evidence|mutation_events"): + parsed.func(parsed) + task_data, _ = execution_context._read_structured(task) + assert task_data.get("status") != "Completed" + + +@pytest.mark.parametrize("command", ["validate-executor-result", "set-plan-status"]) +def test_completed_task_cli_accepts_complete_no_controller_mutation_evidence( + tmp_path: Path, command: str +) -> None: + root, task, _, handoff, _ = _counted_validation(tmp_path) + handoff_path = root / ".work-bundle/orchestration/handoff/executor/active/handoff-task-004.yaml" + handoff_path.write_text( + "\n".join(execution_context._dump_yaml(handoff)) + "\n", encoding="utf-8" + ) + if command == "validate-executor-result": + operation = [command, "--task", str(task), "--handoff", str(handoff_path)] + else: + operation = [ + command, + "--id", + "task-004", + "--kind", + "task", + "--plan-id", + "plan-001", + "--status", + "Completed", + "--handoff", + str(handoff_path), + ] + parsed = orchestration_dispatcher.build_parser().parse_args( + [*operation, "--project-root", str(root), "--mutation-events", "[]"] + ) + parsed.func(parsed) + if command == "set-plan-status": + task_data, _ = execution_context._read_structured(task) + assert task_data["status"] == "Completed" + + @pytest.mark.parametrize( "command", ["validate-executor-result", "set-plan-status", "build-review-package"] ) @@ -747,6 +815,7 @@ def args(root: Path, task: Path, **overrides: object) -> argparse.Namespace: "execution_id": None, "repository_id": None, "execution_runtime_root": None, + "mutation_events": [], } values.update(overrides) return argparse.Namespace(**values) @@ -2232,7 +2301,9 @@ def test_validate_executor_result_accepts_explicit_shaped_no_index_evidence(tmp_ handoff = _read_handoff(_completed_handoff_payload(root)) brief = _compiled_brief(root, task) - validated = execution_context.validate_executor_result_for_task(handoff, brief) + validated = execution_context.validate_executor_result_for_task( + handoff, brief, mutation_events=[] + ) assert validated["evidence_applicability"] == brief["evidence_applicability"] @@ -2276,7 +2347,9 @@ def test_helper_observation_accepts_metadata_only_applicability_without_codegrap "baseline_status": "current", } - validated = execution_context.validate_executor_result_for_task(handoff, brief, observe=True) + validated = execution_context.validate_executor_result_for_task( + handoff, brief, observe=True, mutation_events=[] + ) assert validated["evidence_applicability"]["codegraph"]["required"] is False @@ -2524,6 +2597,99 @@ def test_rf_task_repair_completion_rejects_unsequenced_acceptance_review() -> No execution_context.validate_executor_result_for_task(handoff, brief) +def _material_reset_task_review() -> dict: + prior_identity = { + "artifact_id": "task-rf", "revision": "1", "sha256": "1" * 64, "source_tree": None, + } + current_identity = { + "artifact_id": "task-rf", "revision": "1", "sha256": "2" * 64, "source_tree": None, + } + prior = { + "required": True, + "reviewer_independent": True, + "verdict": "repair", + "review_id": "review-prior-material", + "review_mode": "initial", + "review_target_kind": "task", + "repair_frontier": None, + "review_reset": None, + "target_identity": prior_identity, + "reviewer": { + "agent_id": "reviewer-prior", + "capability": "judgment", + "authorship": "none", + "repair_participation": "none", + "decision_participation": "none", + "deliberation_participation": "none", + "context_origin": "direct_source", + }, + "evidence": { + "mode": "direct", "capabilities": ["source inspection"], + "unavailable_evidence": [], "commands": [], "artifacts": [], + }, + "findings": [{ + "finding_id": "RF-MATERIAL-1", + "stage": "implementation", + "class": "implementation_defect", + "severity": "blocking", + "first_broken_artifact": "implementation", + "obligation_basis": "accepted_requirement", + "evidence": [{ + "kind": "test", "locator": "RF", "digest_or_identity": "RF-RED", + "observation": "failed", + }], + "target_identity": prior_identity, + "summary": "Accepted boundary changed materially.", + "recommended_owner": "task_owner", + "disposition": "repair_task", + }], + "started_at": "2026-09-06T00:00:00Z", + "completed_at": "2026-09-06T00:01:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + return { + "required": True, + "reviewer_independent": True, + "verdict": "accept", + "review_id": "review-current-material-reset", + "review_mode": "initial", + "review_target_kind": "task", + "repair_frontier": None, + "review_reset": { + "prior_review_id": prior["review_id"], + "reason_class": "scope", + "reason": "Accepted write scope changed.", + }, + "target_identity": current_identity, + "reviewer": {**prior["reviewer"], "agent_id": "reviewer-reset"}, + "evidence": { + "mode": "direct", "capabilities": ["source inspection"], + "unavailable_evidence": [], "commands": [], "artifacts": [], + }, + "findings": [], + "previous_review": prior, + "started_at": "2026-09-06T00:02:00Z", + "completed_at": "2026-09-06T00:03:00Z", + "staleness": {"is_stale": False, "reason": None, "supersedes": None}, + } + + +def test_rf_repaired_task_completion_accepts_native_fresh_initial_review_reset() -> None: + brief, handoff = _repair_completion_fixture() + handoff["acceptance_review"] = _material_reset_task_review() + + execution_context._assert_handoff_review_matches_task(handoff, brief, "completed") + + +def test_rf_repaired_task_completion_rejects_unclassified_initial_review_reset() -> None: + brief, handoff = _repair_completion_fixture() + handoff["acceptance_review"] = _material_reset_task_review() + handoff["acceptance_review"]["review_reset"]["reason_class"] = "fixture_recovery" + + with pytest.raises(SystemExit, match="review_reset|material_change"): + execution_context._assert_handoff_review_matches_task(handoff, brief, "completed") + + def test_rf_task_repair_completion_rejects_stale_repair_frontier(tmp_path: Path) -> None: brief, handoff = _repair_completion_fixture() git(tmp_path, "init", "-q") @@ -2656,6 +2822,7 @@ def test_set_plan_status_completed_requires_validated_handoff(tmp_path: Path) -> status="Completed", kind="task", handoff=str(handoff), + mutation_events=[], ) ) data, _ = execution_context._read_structured(task) @@ -2975,7 +3142,9 @@ def _enable_passing_observation(root: Path, task: Path, handoff: Path) -> None: def _validate_observed(handoff: dict, brief: dict) -> dict: - return execution_context.validate_executor_result_for_task(handoff, brief, observe=True) + return execution_context.validate_executor_result_for_task( + handoff, brief, observe=True, mutation_events=[] + ) def _set_process_validation(task: Path, command: str, **fields: object) -> None: @@ -3349,6 +3518,7 @@ def test_set_plan_status_completed_observes_bound_worktree(tmp_path: Path) -> No execution_id=None, repository_id=None, execution_runtime_root=None, + mutation_events=[], ) ) data, _ = execution_context._read_structured(task) diff --git a/tests/test_wor108_context_projection.py b/tests/test_wor108_context_projection.py index 874d791..16a1546 100644 --- a/tests/test_wor108_context_projection.py +++ b/tests/test_wor108_context_projection.py @@ -281,7 +281,9 @@ def deny_path_bytes(file: Path) -> bytes: monkeypatch.setattr(builtins, "open", deny_runtime_retrieval) monkeypatch.setattr(Path, "read_text", deny_path_text) monkeypatch.setattr(Path, "read_bytes", deny_path_bytes) - accepted = execution_context.validate_executor_result_for_task(handoff, compiled_packet) + accepted = execution_context.validate_executor_result_for_task( + handoff, compiled_packet, mutation_events=[] + ) assert accepted["result_state"] == "completed" assert accepted["task_ownership"]["agent_id"] == "ctx-fixture-agent" @@ -312,7 +314,9 @@ def test_completed_task_acceptance_requires_subagent_delegation_evidence(tmp_pat } with pytest.raises(SystemExit, match="workspace-blocked.*subagent ownership"): - execution_context.validate_executor_result_for_task(handoff, brief) + execution_context.validate_executor_result_for_task( + handoff, brief, mutation_events=[] + ) def test_completed_task_acceptance_rejects_controller_task_scope_mutation( @@ -392,6 +396,7 @@ def test_repair_acceptance_requires_exact_runtime_owner_and_continuity( accepted = execution_context.validate_executor_result_for_task( handoff, brief, + mutation_events=[], prior_ownership=prior, repair_continuity=continuity, ) @@ -407,12 +412,14 @@ def test_repair_acceptance_requires_exact_runtime_owner_and_continuity( execution_context.validate_executor_result_for_task( replacement, brief, + mutation_events=[], prior_ownership=prior, repair_continuity=continuity, ) execution_context.validate_executor_result_for_task( replacement, brief, + mutation_events=[], prior_ownership=prior, repair_continuity=continuity, authorized_replacements={brief["task_id"]}, @@ -424,6 +431,7 @@ def test_repair_acceptance_requires_exact_runtime_owner_and_continuity( execution_context.validate_executor_result_for_task( handoff, brief, + mutation_events=[], prior_ownership=prior, repair_continuity=stale, ) @@ -527,9 +535,15 @@ def test_accepted_dependency_deltas_use_exact_handoff_and_observed_checkpoint( } with pytest.raises(SystemExit, match="workflow.md"): - execution_context.validate_executor_result_for_task(handoff, current, observe=True) + execution_context.validate_executor_result_for_task( + handoff, current, observe=True, mutation_events=[] + ) accepted = execution_context.validate_executor_result_for_task( - handoff, current, observe=True, accepted_dependency_deltas=[descriptor] + handoff, + current, + observe=True, + mutation_events=[], + accepted_dependency_deltas=[descriptor], ) assert accepted["result_state"] == "completed" From 89537dfa519db0fc2deec9f77ae564b388352139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 13:14:58 +0800 Subject: [PATCH 31/36] test(wor105): bind transition to accepted release --- .../components/native-transition-record.yaml | 6 ++--- evals/wor105/freeze-manifest.json | 2 +- evals/wor105/results.jsonl | 24 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/evals/wor105/components/native-transition-record.yaml b/evals/wor105/components/native-transition-record.yaml index a70d666..18c1e41 100644 --- a/evals/wor105/components/native-transition-record.yaml +++ b/evals/wor105/components/native-transition-record.yaml @@ -2,11 +2,11 @@ schema: wor105-native-transition-v1 issue: WOR-105 transition_task: task-b06r enforcement_transition: bootstrap_policy_to_native -source_identity: 10aa3aceeea4c440feaccb484b897af67cd0df0b+repository-evidence-sha256:279f142fad6ea3005370b9eaa82d7c88444032e668309173e9230754b28c40ae +source_identity: 9dce5df221485174d6179f713e8b179bbc20567a+repository-evidence-sha256:279f142fad6ea3005370b9eaa82d7c88444032e668309173e9230754b28c40ae review_path: .work-bundle/orchestration/reviews/WOR-105-task-b06r-kernel-review-accepted.yaml review_sha256: d638b8959b4db57dd33e072b01428d6ce89f65a9771c05eff26d8e40d4293ebd -accepted_commit: 10aa3aceeea4c440feaccb484b897af67cd0df0b -accepted_tree: 3f70163e3aef240b0f1207f43433874c9139a7fb +accepted_commit: 9dce5df221485174d6179f713e8b179bbc20567a +accepted_tree: 5a1f38355eae8068bab528923e807ce54e6f6fe5 integrated_validation: {id: VAL-B06R-TEST, result: passed, tests: 300} handoff_validation: {id: VAL-B06R-IDENTITY, result: passed, adversarial_cases: 5} participant_handoffs: diff --git a/evals/wor105/freeze-manifest.json b/evals/wor105/freeze-manifest.json index fc80a83..6cb30cb 100644 --- a/evals/wor105/freeze-manifest.json +++ b/evals/wor105/freeze-manifest.json @@ -72,7 +72,7 @@ "components": { "profile": { "path": "evals/wor105/components/native-transition-record.yaml", - "sha256": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56" + "sha256": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a" }, "fixtures": { "path": "evals/wor105/fixtures", diff --git a/evals/wor105/results.jsonl b/evals/wor105/results.jsonl index 7de1172..1c4c932 100644 --- a/evals/wor105/results.jsonl +++ b/evals/wor105/results.jsonl @@ -1,12 +1,12 @@ -{"actual_decision": "deny_mutation_and_protected_reads_allow_bounded_evidence", "adjudication_sha256": "f256137ea263da8806b4b1a33a3a7510a48e3c6672c8262dc75aa4a26ffc71e4", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-01:mutation-denial:1", "event:ADV-01:mutation-denial:2"], "expected_decision": "deny_mutation_and_protected_reads_allow_bounded_evidence", "fixture_id": "ADV-01", "fixture_sha256": "52e6a3f83058f820fe01343e5b009940842874f05bdf47aa2e99ea60a27127bb", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"allowed_read_output_sha256": "1dc07dc04e672df4c66bca312cc8aa0fd3845e9117eb9c3722c805ba49cad7c4", "control_sentinel_after_sha256": "1e9c7b842690803f3f25a996afcd18348e571ef93083e2094f7c533dbdef0397", "control_sentinel_before_sha256": "1e9c7b842690803f3f25a996afcd18348e571ef93083e2094f7c533dbdef0397", "denial_classes": ["permission_denied", "permission_denied", "permission_denied", "permission_denied", "permission_denied"], "event_ids": ["event:ADV-01:mutation-denial:1", "event:ADV-01:mutation-denial:2"], "source_sentinel_after_sha256": "b2271b177e020061b04fc194bf5ec85a9cdbfc9e74074b73b52d76a5fc2af85a", "source_sentinel_before_sha256": "b2271b177e020061b04fc194bf5ec85a9cdbfc9e74074b73b52d76a5fc2af85a", "validator_output_sha256": "4caeb0c30f3bd412655341504b3b564ce92c160c17e419c5251d8b61a9c8f259"}, "raw_evidence_sha256": "4f2253688acd2c03346731731e6b10a9b3f95c991f91125ad00acf7e01e01d7d", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "pause_and_reslice_after_second_expansion", "adjudication_sha256": "0c312e858a1dabac1efc47526ba05dd56a25c4351574d543104213fe4fef87b6", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-02:1"], "expected_decision": "pause_and_reslice_after_second_expansion", "fixture_id": "ADV-02", "fixture_sha256": "78fe6de27c7833336f6883a73dc61aaa0b15b37735ae639e9061e071e0dfcd40", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"binding_state": "repair_owned", "expansion_event_ids": ["event:ADV-02:expand:1", "event:ADV-02:expand:2"], "original_evidence_sha256": "dbe1e53e72d77259941220006b4dabe76e616d22ae79027474eacbc4312e67a0", "reslice_artifact_sha256": "cf7240e355ba73ddef7d7376e813c8fb4d4d462b40409bdf9a933fc241dd3d6e", "return_owner": "plan_owner"}, "raw_evidence_sha256": "29d9edb9048ae76009eb3a23e5c83665d2d3dd8335ca9e24f5c8399a42a47662", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "reject_and_route_allocation_gap_to_plan_reslice", "adjudication_sha256": "51468752b9157242d57bb4bb7c23937578afbd08933b94f7fca98b86ab8fabae", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-03:1"], "expected_decision": "reject_and_route_allocation_gap_to_plan_reslice", "fixture_id": "ADV-03", "fixture_sha256": "490ebdd8890adb88bd52a6cd24a54d02bdc6750d0a3f9cc63e78cc6a2d68d260", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"canonical_finding_sha256": "b18f342fdb3df678ce2df6d512fcdb70739adce75c9cd0cb38d713a8435d20f5", "rejected_record_sha256": "c3800c532270ebf1766019851e6edc82a13b9ce0901668bb0f4b7fa99bfd6df0", "validation_error_code": "finding_route_mismatch"}, "raw_evidence_sha256": "5ce46fcc3d48798e1aeceb6b01e206612cf0e45066968d898e8d8d601fc16c6c", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "reject_blocking_and_record_nonblocking_advisory", "adjudication_sha256": "8f68aa67e626e52e4d9e901dda96c65a95db4f72ced495e1d6a76ea587c442b3", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-04:1"], "expected_decision": "reject_blocking_and_record_nonblocking_advisory", "fixture_id": "ADV-04", "fixture_sha256": "19db36abd1deb09f95179b572e2c559509aea0a2607aa1a04cd4b5af7db291ef", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"advisory_id": "advisory:ADV-04", "stage_state_after_sha256": "7f11ee6a1f8fc67e343ff9ede27759613559df69e153fefa0540dc55281a2df6", "stage_state_before_sha256": "7f11ee6a1f8fc67e343ff9ede27759613559df69e153fefa0540dc55281a2df6", "validation_error_code": "blocking_basis_required"}, "raw_evidence_sha256": "741437d7d693ed2aaeba457eab8f7dfa216cb52e26a1e43245882052439b2213", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "stale_run_append_invalidation_preserve_raw_evidence", "adjudication_sha256": "a4fd0509b02c28e16a858b9c277227da3585eafe562992594f5c6f8683c9dc44", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-05:1"], "expected_decision": "stale_run_append_invalidation_preserve_raw_evidence", "fixture_id": "ADV-05", "fixture_sha256": "42251cea32fd52341f87067be6bbc4194da7139b65b823dcf230b11f50baa3f7", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"invalidation_id": "invalidation:ADV-05", "new_digest": "da9456aece01c51674e58791a55e81a9357e4c6dd14de537038a668b1c26c2ec", "old_digest": "de0043aa39f8969804ba2e21f6abbc4a5eb50bf22177c9ce60e8807ec1fe671b", "raw_response_after_sha256": "1a16053dccca94ba3e07bf3f1119c0a3387364cecc59752c2ab4a7d734f9de4f", "raw_response_before_sha256": "1a16053dccca94ba3e07bf3f1119c0a3387364cecc59752c2ab4a7d734f9de4f", "raw_trace_after_sha256": "68245e9a19559d24fa246ec87f100351c7e2f97b88374254956d08611ef3a84d", "raw_trace_before_sha256": "68245e9a19559d24fa246ec87f100351c7e2f97b88374254956d08611ef3a84d", "stale_run_id": "run:ADV-05"}, "raw_evidence_sha256": "0821a85ca0f258bd769bc848136420c9c46035322e9e1cc85726da32c8648ef7", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "preserve_product_observation_update_packaging_only", "adjudication_sha256": "b784bb58f6b27be4f92782776647fb3985d99a5cc07b0f9f4c3e276e8c5bde20", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-06:1"], "expected_decision": "preserve_product_observation_update_packaging_only", "fixture_id": "ADV-06", "fixture_sha256": "f5241e874cd284c3e41ecab7e2790d265a8ca7f0e53785d9bb151ebeefb16d6e", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"observation_after_sha256": "b5729bf53e81f9a32161b5e412cdc1ef24259d833f1cbc448caed5f0733dc8ea", "observation_before_sha256": "b5729bf53e81f9a32161b5e412cdc1ef24259d833f1cbc448caed5f0733dc8ea", "packaging_after": "3333333333333333333333333333333333333333", "packaging_before": "2222222222222222222222222222222222222222", "product_tree_after": "1111111111111111111111111111111111111111", "product_tree_before": "1111111111111111111111111111111111111111", "valid": true}, "raw_evidence_sha256": "4ee5d2cf39b9f696bf9ca32a6c246de0c0488656d3dc6c4b65a08fbba28a2676", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "mark_review_stale_and_remove_stage_credit", "adjudication_sha256": "6d06a92a211f065ba1216c44062d06fa78b49cb914d2d81cfa5a2a656344e64c", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-07:1"], "expected_decision": "mark_review_stale_and_remove_stage_credit", "fixture_id": "ADV-07", "fixture_sha256": "f9446f50289c62fd1115d89d2121576950174cbad68bb4e76cdd8cef1a962d47", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"countable_stage_reviews": 0, "review_id": "review:ADV-07", "staleness_reason": "target_identity_changed", "target_after_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "target_before_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "raw_evidence_sha256": "fc968bdfbc46c514d3c0965e0b19d7601af03bbf35c40c9ac67fb2e2e76238e4", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "execute_once_and_reuse_observation", "adjudication_sha256": "4216f0afd1e4634a240f548cc0ecc0872aeb8363c53d43e7f518211d6e84e94c", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-08:1"], "expected_decision": "execute_once_and_reuse_observation", "fixture_id": "ADV-08", "fixture_sha256": "b8b07a687c7d492ad983b88a72b837f18ae50b6c384aa394a732c60e940bd01e", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"observation_id": "observation:430c6f98184ff990ddfd", "request_ids": ["request:ADV-08:1", "request:ADV-08:2"], "reuse_of": "observation:430c6f98184ff990ddfd", "subprocess_invocation_count": 1}, "raw_evidence_sha256": "6ba81e81dfb301c7c481b9987e7ea1c5fbd55c362e2fa63b79aa83dbc4e0ad86", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "deny_release_preserve_owner_reason_history", "adjudication_sha256": "3abf36eb3dc3ccc6f1ff4616598825e2174ab8774b2b0a5931508c347a58462d", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-09:1"], "expected_decision": "deny_release_preserve_owner_reason_history", "fixture_id": "ADV-09", "fixture_sha256": "5a347c746583f3bc1175b50aa5b2fdf8250e07a929206d777a48bdb7e50ca70f", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"after_snapshot_sha256": "a34c0d2062af8cb91d18210f9008b41fbc70bf54d0149bea6f6c2d22eb116179", "before_snapshot_sha256": "a34c0d2062af8cb91d18210f9008b41fbc70bf54d0149bea6f6c2d22eb116179", "denial_event_ids": ["event:ADV-09:repair", "event:ADV-09:rereview"], "original_owner": "repair_owned", "original_reason": "binding retained by active repair owner"}, "raw_evidence_sha256": "79fe7d310cfde0ffccfdf4004232ff2d47be0f14ecac5031f69fff34077e1d33", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "reject_each_and_require_fresh_reviewer", "adjudication_sha256": "510e35f8ad9d7560c08c15820c68e04a65f6bad545c201b2d23856a418ba6008", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-10:1"], "expected_decision": "reject_each_and_require_fresh_reviewer", "fixture_id": "ADV-10", "fixture_sha256": "7ea520dcefa78d9348f354b1a9dcc9726798a0759b0b768d1ae476770731c673", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"countable_stage_reviews": 0, "rejected_review_ids": ["review:ADV-10:authorship", "review:ADV-10:repair_participation", "review:ADV-10:decision_participation", "review:ADV-10:deliberation_participation"], "validation_error_codes": ["reviewer_not_independent", "reviewer_not_independent", "reviewer_not_independent", "reviewer_not_independent"]}, "raw_evidence_sha256": "8b72d990c3945f0cccb06a95663374d559c4711da54a895dca554d179912da4c", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "route_validation_oracle_defect_without_product_rollback", "adjudication_sha256": "921bb49dae8fcf29f6bbe956ca146f8d91b6c16f6c7a93f4ba5ee9687ee2609d", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-11:1"], "expected_decision": "route_validation_oracle_defect_without_product_rollback", "fixture_id": "ADV-11", "fixture_sha256": "4520983615ce735ab5cd6ba1729e464085e0af0ec2f152fcf32872e201041ef7", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"byte_oracle_failure_sha256": "3513611273bb3e74c5c8a7224ddae25322904f08dd42e595a47ed8c9120aed73", "product_revision_after": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "product_revision_before": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "public_contract_test_output_sha256": "ec996444a9a62cde22b0ee5e1713a679f65f9aac74f285bd0ecdb9c04ff01e00", "routed_finding_sha256": "11d26026ee1ecc06e4418dc9e850d5e09d185ab8638bdd6edbfa41a60d14336b"}, "raw_evidence_sha256": "02671b359b2c0d23649cfd9e99874413da954bd8b4ff3bb08d9b65e3b844bd24", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} -{"actual_decision": "reject_placeholder_apply_deferred_without_checkout_or_origin_and_replay_noop", "adjudication_sha256": "dd2390df12ef6f7363a688d9e96424889975274a8be62c9cf99cab34136500fb", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "aa01163346022ddc9b55481cf1698614da08a1c0f75a98d38b5ce3008b57cd56", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-12:1"], "expected_decision": "reject_placeholder_apply_deferred_without_checkout_or_origin_and_replay_noop", "fixture_id": "ADV-12", "fixture_sha256": "7f29996db2939960f308f7bfe72e139e3eded355804a72860b98add5d0ce0e18", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"checkout_absent": true, "first_apply_state_sha256": "d1ac29c296e400e6b4ea3e95b4024be6dc436c5ce10793a224591ee17be1d12d", "member_snapshot_sha256": "d1ac29c296e400e6b4ea3e95b4024be6dc436c5ce10793a224591ee17be1d12d", "origin_absent": true, "replay_state_sha256": "d1ac29c296e400e6b4ea3e95b4024be6dc436c5ce10793a224591ee17be1d12d", "validation_error_code": "placeholder_remote_forbidden"}, "raw_evidence_sha256": "83ea6e80a7289ec65e87c0c694c11b8903bff0a8339085ff31a8391b2790be50", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "deny_mutation_and_protected_reads_allow_bounded_evidence", "adjudication_sha256": "f256137ea263da8806b4b1a33a3a7510a48e3c6672c8262dc75aa4a26ffc71e4", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-01:mutation-denial:1", "event:ADV-01:mutation-denial:2"], "expected_decision": "deny_mutation_and_protected_reads_allow_bounded_evidence", "fixture_id": "ADV-01", "fixture_sha256": "52e6a3f83058f820fe01343e5b009940842874f05bdf47aa2e99ea60a27127bb", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"allowed_read_output_sha256": "1dc07dc04e672df4c66bca312cc8aa0fd3845e9117eb9c3722c805ba49cad7c4", "control_sentinel_after_sha256": "1e9c7b842690803f3f25a996afcd18348e571ef93083e2094f7c533dbdef0397", "control_sentinel_before_sha256": "1e9c7b842690803f3f25a996afcd18348e571ef93083e2094f7c533dbdef0397", "denial_classes": ["permission_denied", "permission_denied", "permission_denied", "permission_denied", "permission_denied"], "event_ids": ["event:ADV-01:mutation-denial:1", "event:ADV-01:mutation-denial:2"], "source_sentinel_after_sha256": "b2271b177e020061b04fc194bf5ec85a9cdbfc9e74074b73b52d76a5fc2af85a", "source_sentinel_before_sha256": "b2271b177e020061b04fc194bf5ec85a9cdbfc9e74074b73b52d76a5fc2af85a", "validator_output_sha256": "4caeb0c30f3bd412655341504b3b564ce92c160c17e419c5251d8b61a9c8f259"}, "raw_evidence_sha256": "4f2253688acd2c03346731731e6b10a9b3f95c991f91125ad00acf7e01e01d7d", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "pause_and_reslice_after_second_expansion", "adjudication_sha256": "0c312e858a1dabac1efc47526ba05dd56a25c4351574d543104213fe4fef87b6", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-02:1"], "expected_decision": "pause_and_reslice_after_second_expansion", "fixture_id": "ADV-02", "fixture_sha256": "78fe6de27c7833336f6883a73dc61aaa0b15b37735ae639e9061e071e0dfcd40", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"binding_state": "repair_owned", "expansion_event_ids": ["event:ADV-02:expand:1", "event:ADV-02:expand:2"], "original_evidence_sha256": "dbe1e53e72d77259941220006b4dabe76e616d22ae79027474eacbc4312e67a0", "reslice_artifact_sha256": "cf7240e355ba73ddef7d7376e813c8fb4d4d462b40409bdf9a933fc241dd3d6e", "return_owner": "plan_owner"}, "raw_evidence_sha256": "29d9edb9048ae76009eb3a23e5c83665d2d3dd8335ca9e24f5c8399a42a47662", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "reject_and_route_allocation_gap_to_plan_reslice", "adjudication_sha256": "51468752b9157242d57bb4bb7c23937578afbd08933b94f7fca98b86ab8fabae", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-03:1"], "expected_decision": "reject_and_route_allocation_gap_to_plan_reslice", "fixture_id": "ADV-03", "fixture_sha256": "490ebdd8890adb88bd52a6cd24a54d02bdc6750d0a3f9cc63e78cc6a2d68d260", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"canonical_finding_sha256": "b18f342fdb3df678ce2df6d512fcdb70739adce75c9cd0cb38d713a8435d20f5", "rejected_record_sha256": "c3800c532270ebf1766019851e6edc82a13b9ce0901668bb0f4b7fa99bfd6df0", "validation_error_code": "finding_route_mismatch"}, "raw_evidence_sha256": "5ce46fcc3d48798e1aeceb6b01e206612cf0e45066968d898e8d8d601fc16c6c", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "reject_blocking_and_record_nonblocking_advisory", "adjudication_sha256": "8f68aa67e626e52e4d9e901dda96c65a95db4f72ced495e1d6a76ea587c442b3", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-04:1"], "expected_decision": "reject_blocking_and_record_nonblocking_advisory", "fixture_id": "ADV-04", "fixture_sha256": "19db36abd1deb09f95179b572e2c559509aea0a2607aa1a04cd4b5af7db291ef", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"advisory_id": "advisory:ADV-04", "stage_state_after_sha256": "7f11ee6a1f8fc67e343ff9ede27759613559df69e153fefa0540dc55281a2df6", "stage_state_before_sha256": "7f11ee6a1f8fc67e343ff9ede27759613559df69e153fefa0540dc55281a2df6", "validation_error_code": "blocking_basis_required"}, "raw_evidence_sha256": "741437d7d693ed2aaeba457eab8f7dfa216cb52e26a1e43245882052439b2213", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "stale_run_append_invalidation_preserve_raw_evidence", "adjudication_sha256": "a4fd0509b02c28e16a858b9c277227da3585eafe562992594f5c6f8683c9dc44", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-05:1"], "expected_decision": "stale_run_append_invalidation_preserve_raw_evidence", "fixture_id": "ADV-05", "fixture_sha256": "42251cea32fd52341f87067be6bbc4194da7139b65b823dcf230b11f50baa3f7", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"invalidation_id": "invalidation:ADV-05", "new_digest": "da9456aece01c51674e58791a55e81a9357e4c6dd14de537038a668b1c26c2ec", "old_digest": "de0043aa39f8969804ba2e21f6abbc4a5eb50bf22177c9ce60e8807ec1fe671b", "raw_response_after_sha256": "1a16053dccca94ba3e07bf3f1119c0a3387364cecc59752c2ab4a7d734f9de4f", "raw_response_before_sha256": "1a16053dccca94ba3e07bf3f1119c0a3387364cecc59752c2ab4a7d734f9de4f", "raw_trace_after_sha256": "68245e9a19559d24fa246ec87f100351c7e2f97b88374254956d08611ef3a84d", "raw_trace_before_sha256": "68245e9a19559d24fa246ec87f100351c7e2f97b88374254956d08611ef3a84d", "stale_run_id": "run:ADV-05"}, "raw_evidence_sha256": "0821a85ca0f258bd769bc848136420c9c46035322e9e1cc85726da32c8648ef7", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "preserve_product_observation_update_packaging_only", "adjudication_sha256": "b784bb58f6b27be4f92782776647fb3985d99a5cc07b0f9f4c3e276e8c5bde20", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-06:1"], "expected_decision": "preserve_product_observation_update_packaging_only", "fixture_id": "ADV-06", "fixture_sha256": "f5241e874cd284c3e41ecab7e2790d265a8ca7f0e53785d9bb151ebeefb16d6e", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"observation_after_sha256": "b5729bf53e81f9a32161b5e412cdc1ef24259d833f1cbc448caed5f0733dc8ea", "observation_before_sha256": "b5729bf53e81f9a32161b5e412cdc1ef24259d833f1cbc448caed5f0733dc8ea", "packaging_after": "3333333333333333333333333333333333333333", "packaging_before": "2222222222222222222222222222222222222222", "product_tree_after": "1111111111111111111111111111111111111111", "product_tree_before": "1111111111111111111111111111111111111111", "valid": true}, "raw_evidence_sha256": "4ee5d2cf39b9f696bf9ca32a6c246de0c0488656d3dc6c4b65a08fbba28a2676", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "mark_review_stale_and_remove_stage_credit", "adjudication_sha256": "6d06a92a211f065ba1216c44062d06fa78b49cb914d2d81cfa5a2a656344e64c", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-07:1"], "expected_decision": "mark_review_stale_and_remove_stage_credit", "fixture_id": "ADV-07", "fixture_sha256": "f9446f50289c62fd1115d89d2121576950174cbad68bb4e76cdd8cef1a962d47", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"countable_stage_reviews": 0, "review_id": "review:ADV-07", "staleness_reason": "target_identity_changed", "target_after_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "target_before_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "raw_evidence_sha256": "fc968bdfbc46c514d3c0965e0b19d7601af03bbf35c40c9ac67fb2e2e76238e4", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "execute_once_and_reuse_observation", "adjudication_sha256": "4216f0afd1e4634a240f548cc0ecc0872aeb8363c53d43e7f518211d6e84e94c", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-08:1"], "expected_decision": "execute_once_and_reuse_observation", "fixture_id": "ADV-08", "fixture_sha256": "b8b07a687c7d492ad983b88a72b837f18ae50b6c384aa394a732c60e940bd01e", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"observation_id": "observation:430c6f98184ff990ddfd", "request_ids": ["request:ADV-08:1", "request:ADV-08:2"], "reuse_of": "observation:430c6f98184ff990ddfd", "subprocess_invocation_count": 1}, "raw_evidence_sha256": "6ba81e81dfb301c7c481b9987e7ea1c5fbd55c362e2fa63b79aa83dbc4e0ad86", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "deny_release_preserve_owner_reason_history", "adjudication_sha256": "3abf36eb3dc3ccc6f1ff4616598825e2174ab8774b2b0a5931508c347a58462d", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-09:1"], "expected_decision": "deny_release_preserve_owner_reason_history", "fixture_id": "ADV-09", "fixture_sha256": "5a347c746583f3bc1175b50aa5b2fdf8250e07a929206d777a48bdb7e50ca70f", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"after_snapshot_sha256": "a34c0d2062af8cb91d18210f9008b41fbc70bf54d0149bea6f6c2d22eb116179", "before_snapshot_sha256": "a34c0d2062af8cb91d18210f9008b41fbc70bf54d0149bea6f6c2d22eb116179", "denial_event_ids": ["event:ADV-09:repair", "event:ADV-09:rereview"], "original_owner": "repair_owned", "original_reason": "binding retained by active repair owner"}, "raw_evidence_sha256": "79fe7d310cfde0ffccfdf4004232ff2d47be0f14ecac5031f69fff34077e1d33", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "reject_each_and_require_fresh_reviewer", "adjudication_sha256": "510e35f8ad9d7560c08c15820c68e04a65f6bad545c201b2d23856a418ba6008", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-10:1"], "expected_decision": "reject_each_and_require_fresh_reviewer", "fixture_id": "ADV-10", "fixture_sha256": "7ea520dcefa78d9348f354b1a9dcc9726798a0759b0b768d1ae476770731c673", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"countable_stage_reviews": 0, "rejected_review_ids": ["review:ADV-10:authorship", "review:ADV-10:repair_participation", "review:ADV-10:decision_participation", "review:ADV-10:deliberation_participation"], "validation_error_codes": ["reviewer_not_independent", "reviewer_not_independent", "reviewer_not_independent", "reviewer_not_independent"]}, "raw_evidence_sha256": "8b72d990c3945f0cccb06a95663374d559c4711da54a895dca554d179912da4c", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "route_validation_oracle_defect_without_product_rollback", "adjudication_sha256": "921bb49dae8fcf29f6bbe956ca146f8d91b6c16f6c7a93f4ba5ee9687ee2609d", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-11:1"], "expected_decision": "route_validation_oracle_defect_without_product_rollback", "fixture_id": "ADV-11", "fixture_sha256": "4520983615ce735ab5cd6ba1729e464085e0af0ec2f152fcf32872e201041ef7", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"byte_oracle_failure_sha256": "3513611273bb3e74c5c8a7224ddae25322904f08dd42e595a47ed8c9120aed73", "product_revision_after": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "product_revision_before": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "public_contract_test_output_sha256": "ec996444a9a62cde22b0ee5e1713a679f65f9aac74f285bd0ecdb9c04ff01e00", "routed_finding_sha256": "11d26026ee1ecc06e4418dc9e850d5e09d185ab8638bdd6edbfa41a60d14336b"}, "raw_evidence_sha256": "02671b359b2c0d23649cfd9e99874413da954bd8b4ff3bb08d9b65e3b844bd24", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} +{"actual_decision": "reject_placeholder_apply_deferred_without_checkout_or_origin_and_replay_noop", "adjudication_sha256": "dd2390df12ef6f7363a688d9e96424889975274a8be62c9cf99cab34136500fb", "component_digests": {"evidence_capabilities": "8f8d87d1f2f9183fae250519122632bcce3ee4dae5917b1e99a1384046142b76", "fixtures": "bc4568dae581546c21f60275ad4dc60ae762df36238b98cf89b569f40edde5f2", "instructions": "5cc2ba1160a25b767a7e02c957d6f37a14a7fa4be50c711f5b67c5c03442d66a", "profile": "0d39d8709d8b4884a93384a51829d147e465dbf674e2c122d324d67c1283205a", "result_schema": "ef4a96e1682e29e7225dbecf4257e3dad040bff2d549eff1b71067033db64c89", "runner": "c4d0d310ebced9819c4f28b6fe8b5e9fbd9d8d0fdfaf3a08564e974abfce7653", "semantic_schema": "ab6500c1197928450da7e7a438d69d763c376332aeaa72caa23526c613eb1b67", "verifier": "08503554ed5e586251a6c722361d8deb5ca03f8b7fb5c81958acd811ff9a29d4"}, "evaluation_id": "wor105-native-adversarial-20260905-012", "event_ids": ["event:ADV-12:1"], "expected_decision": "reject_placeholder_apply_deferred_without_checkout_or_origin_and_replay_noop", "fixture_id": "ADV-12", "fixture_sha256": "7f29996db2939960f308f7bfe72e139e3eded355804a72860b98add5d0ce0e18", "passed": true, "plan_sha256": "c6b2b633f953d62b56e2986f99e4ca87bd83c0e3e863c0cfe32c57a945ddacc8", "product_tree": "dd77419973eb31ca008dd2b04f7dd9dc99d644f3", "proof": {"checkout_absent": true, "first_apply_state_sha256": "d1ac29c296e400e6b4ea3e95b4024be6dc436c5ce10793a224591ee17be1d12d", "member_snapshot_sha256": "d1ac29c296e400e6b4ea3e95b4024be6dc436c5ce10793a224591ee17be1d12d", "origin_absent": true, "replay_state_sha256": "d1ac29c296e400e6b4ea3e95b4024be6dc436c5ce10793a224591ee17be1d12d", "validation_error_code": "placeholder_remote_forbidden"}, "raw_evidence_sha256": "83ea6e80a7289ec65e87c0c694c11b8903bff0a8339085ff31a8391b2790be50", "specification_sha256": "ec701ba8f09a9e8a57bd804b9ab72602c111a50b7091c8b3f54a2d98715f080f", "task_identity": "task-f01r25@plan-20260904-001-wor105-legacy-stabilization"} From b3150679744a6364a31876c14fc9130d4868fb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 13:33:04 +0800 Subject: [PATCH 32/36] fix(orchestration): close acceptance review gaps --- scripts/orchestration/execution_context.py | 14 +++-- scripts/orchestration/task_ownership.py | 25 +++++++-- tests/test_orchestration_execution_context.py | 54 ++++++++++++++++++- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index dc5961f..e5e893b 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -3138,7 +3138,7 @@ def validate_executor_result_for_task( if state in {"completed", "partial"}: _assert_task_fit_check(handoff, task_id, state) _assert_changed_paths_in_write_scope(handoff, task_files) - _assert_handoff_review_matches_task(handoff, task, state) + acceptance_review_sequence = _assert_handoff_review_matches_task(handoff, task, state) required_items = [ item for item in _as_list(task.get("validation")) @@ -3248,7 +3248,7 @@ def validate_executor_result_for_task( validations_passed=True, operation=operation, ) - if operation == "repair": + if operation == "repair" and acceptance_review_sequence != "initial-reset": _validate_repair_acceptance_continuity( task=task, handoff=handoff, @@ -3381,7 +3381,9 @@ def _assert_changed_paths_in_write_scope(handoff: dict[str, Any], task_files: di raise SystemExit(f"Executor result changed path is outside task write scope: {path}") -def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, Any], state: str) -> None: +def _assert_handoff_review_matches_task( + handoff: dict[str, Any], task: dict[str, Any], state: str +) -> str | None: compiled_required = task.get("review_required") is True review = handoff.get("acceptance_review") if isinstance(handoff.get("acceptance_review"), dict) else {} handoff_required = review.get("required") is True @@ -3390,7 +3392,7 @@ def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, if compiled_required and state == "completed" and review.get("verdict") != "accept": raise SystemExit("Review-required task cannot complete without acceptance_review.verdict: accept") if not compiled_required or state != "completed": - return + return None fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} repaired = fit.get("result") == "repaired" mode = review.get("review_mode") @@ -3422,6 +3424,10 @@ def _assert_handoff_review_matches_task(handoff: dict[str, Any], task: dict[str, if (review.get("reviewed_head") != head.stdout.strip() or validated.target_identity["source_tree"] != tree.stdout.strip()): raise SystemExit("Task repair review does not match the observed Git head/tree identity") + if validated.review_mode == "initial" and validated.review_reset is not None: + return "initial-reset" + return validated.review_mode + return None def _yaml_scalar(value: Any) -> str: diff --git a/scripts/orchestration/task_ownership.py b/scripts/orchestration/task_ownership.py index 07298c3..9ce5b6f 100644 --- a/scripts/orchestration/task_ownership.py +++ b/scripts/orchestration/task_ownership.py @@ -3,11 +3,14 @@ from __future__ import annotations from dataclasses import dataclass +from pathlib import PurePosixPath from typing import Iterable, Mapping, Protocol, Sequence PROVENANCE_FIELDS = frozenset({"delegated", "owner_kind", "agent_id", "run_id", "mechanism"}) SUBAGENT_MECHANISMS = frozenset({"host-native", "execution-flow"}) +MUTATION_EVENT_FIELDS = frozenset({"actor_kind", "paths"}) +MUTATION_ACTOR_KINDS = frozenset({"controller", "subagent"}) class OwnershipBlocker(RuntimeError): @@ -139,11 +142,27 @@ def validate_task_acceptance_ownership( provenance = normalize_subagent_provenance(delegation_evidence, operation=operation) for event in mutation_events: - actor_kind = str(event.get("actor_kind") or "").strip() + if set(event) != MUTATION_EVENT_FIELDS: + raise OwnershipBlocker("review-blocked", "mutation event fields are not closed and complete") + actor_kind = event.get("actor_kind") + if not isinstance(actor_kind, str) or actor_kind not in MUTATION_ACTOR_KINDS: + raise OwnershipBlocker("review-blocked", "mutation event actor_kind is invalid") paths = event.get("paths") - if actor_kind != "controller" or not isinstance(paths, Sequence) or isinstance(paths, (str, bytes)): + if not isinstance(paths, list) or not paths: + raise OwnershipBlocker("review-blocked", "mutation event paths must be a non-empty list") + changed: list[str] = [] + for path in paths: + if not isinstance(path, str): + raise OwnershipBlocker("review-blocked", "mutation event paths must contain strings") + normalized = path.strip().removeprefix("./") + parsed = PurePosixPath(normalized) + if (not normalized or parsed.is_absolute() or ".." in parsed.parts + or normalized in {".", "./"} or "\\" in normalized + or any(character in normalized for character in "*?[]")): + raise OwnershipBlocker("review-blocked", "mutation event path is unsafe") + changed.append(normalized) + if actor_kind != "controller": continue - changed = [str(path) for path in paths] if _scopes_overlap(changed, write_scope): raise OwnershipBlocker( "review-blocked", diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 17f2ef7..ff795b5 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -2678,7 +2678,12 @@ def test_rf_repaired_task_completion_accepts_native_fresh_initial_review_reset() brief, handoff = _repair_completion_fixture() handoff["acceptance_review"] = _material_reset_task_review() - execution_context._assert_handoff_review_matches_task(handoff, brief, "completed") + accepted = execution_context.validate_executor_result_for_task( + handoff, brief, mutation_events=[] + ) + + assert accepted["result_state"] == "completed" + assert accepted["task_ownership"]["agent_id"] == "repair-fixture-agent" def test_rf_repaired_task_completion_rejects_unclassified_initial_review_reset() -> None: @@ -2687,7 +2692,52 @@ def test_rf_repaired_task_completion_rejects_unclassified_initial_review_reset() handoff["acceptance_review"]["review_reset"]["reason_class"] = "fixture_recovery" with pytest.raises(SystemExit, match="review_reset|material_change"): - execution_context._assert_handoff_review_matches_task(handoff, brief, "completed") + execution_context.validate_executor_result_for_task( + handoff, brief, mutation_events=[] + ) + + +@pytest.mark.parametrize( + "mutation_events", + [ + [{}], + [{"paths": ["src/a.py"]}], + [{"actor_kind": "controller"}], + [{"actor_kind": 42, "paths": ["src/a.py"]}], + [{"actor_kind": "unknown", "paths": ["src/a.py"]}], + [{"actor_kind": "subagent", "paths": "src/a.py"}], + [{"actor_kind": "subagent", "paths": []}], + [{"actor_kind": "subagent", "paths": [42]}], + [{"actor_kind": "subagent", "paths": ["../src/a.py"]}], + [{"actor_kind": "subagent", "paths": ["/tmp/src/a.py"]}], + [{"actor_kind": "subagent", "paths": ["src/a.py"], "extra": True}], + ], +) +def test_completed_task_rejects_malformed_mutation_evidence( + tmp_path: Path, mutation_events: object +) -> None: + _, _, brief, handoff, _ = _counted_validation(tmp_path) + + with pytest.raises(SystemExit, match="mutation_events|mutation event"): + execution_context.validate_executor_result_for_task( + handoff, + brief, + observe=True, + mutation_events=mutation_events, + ) + + +def test_completed_task_accepts_complete_subagent_mutation_evidence(tmp_path: Path) -> None: + _, _, brief, handoff, _ = _counted_validation(tmp_path) + + accepted = execution_context.validate_executor_result_for_task( + handoff, + brief, + observe=True, + mutation_events=[{"actor_kind": "subagent", "paths": [WRITE_SCOPE_FILE]}], + ) + + assert accepted["result_state"] == "completed" def test_rf_task_repair_completion_rejects_stale_repair_frontier(tmp_path: Path) -> None: From ea0645b4f4066eacdd3670022bf9d56786922dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 13:36:02 +0800 Subject: [PATCH 33/36] test(wor108): include repaired WOR105 evidence surfaces --- evals/wor108/migration-impact.json | 3 +++ tests/test_wor108_closure.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/evals/wor108/migration-impact.json b/evals/wor108/migration-impact.json index 8204bcc..6e5e2cc 100644 --- a/evals/wor108/migration-impact.json +++ b/evals/wor108/migration-impact.json @@ -38,6 +38,9 @@ "scripts/work-bundle/stage_events.py" ], "fixtures_and_evaluations": [ + "evals/wor105/components/native-transition-record.yaml", + "evals/wor105/freeze-manifest.json", + "evals/wor105/results.jsonl", "evals/wor108/contracts-v1.schema.json", "evals/wor108/fixtures.json", "evals/wor108/migration-impact.json", diff --git a/tests/test_wor108_closure.py b/tests/test_wor108_closure.py index 1181038..67e4213 100644 --- a/tests/test_wor108_closure.py +++ b/tests/test_wor108_closure.py @@ -21,7 +21,7 @@ def test_wor108_closure_registry_has_exact_public_fixture_identities() -> None: assert result == { "evaluation_id": "wor108-legacy-closure-v1", "fixtures": 22, - "changed_surfaces": 40, + "changed_surfaces": 43, "verdict": "accepted", } From aff7f7ffa397ecea63487af373edbd3cfb489056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 13:40:40 +0800 Subject: [PATCH 34/36] fix(orchestration): normalize mutation evidence paths --- scripts/orchestration/task_ownership.py | 2 +- tests/test_orchestration_execution_context.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/scripts/orchestration/task_ownership.py b/scripts/orchestration/task_ownership.py index 9ce5b6f..72e8e9f 100644 --- a/scripts/orchestration/task_ownership.py +++ b/scripts/orchestration/task_ownership.py @@ -160,7 +160,7 @@ def validate_task_acceptance_ownership( or normalized in {".", "./"} or "\\" in normalized or any(character in normalized for character in "*?[]")): raise OwnershipBlocker("review-blocked", "mutation event path is unsafe") - changed.append(normalized) + changed.append(parsed.as_posix()) if actor_kind != "controller": continue if _scopes_overlap(changed, write_scope): diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index ff795b5..cf3e3a0 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -2740,6 +2740,36 @@ def test_completed_task_accepts_complete_subagent_mutation_evidence(tmp_path: Pa assert accepted["result_state"] == "completed" +def test_completed_task_rejects_controller_mutation_with_dot_segment() -> None: + brief, handoff = _repair_completion_fixture() + brief["review_required"] = False + brief["files"]["write"] = ["src/a.py"] + handoff["task_fit_check"]["result"] = "clean" + handoff["acceptance_review"] = {"required": False} + + with pytest.raises(SystemExit, match="controller mutated task-owned implementation scope"): + execution_context.validate_executor_result_for_task( + handoff, + brief, + mutation_events=[{"actor_kind": "controller", "paths": ["src/./a.py"]}], + ) + + +def test_completed_task_rejects_controller_mutation_with_repeated_separator() -> None: + brief, handoff = _repair_completion_fixture() + brief["review_required"] = False + brief["files"]["write"] = ["src/a.py"] + handoff["task_fit_check"]["result"] = "clean" + handoff["acceptance_review"] = {"required": False} + + with pytest.raises(SystemExit, match="controller mutated task-owned implementation scope"): + execution_context.validate_executor_result_for_task( + handoff, + brief, + mutation_events=[{"actor_kind": "controller", "paths": ["src//a.py"]}], + ) + + def test_rf_task_repair_completion_rejects_stale_repair_frontier(tmp_path: Path) -> None: brief, handoff = _repair_completion_fixture() git(tmp_path, "init", "-q") From f1d66e064fff01b0ccedf448e6ccf62f005d6572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 13:55:24 +0800 Subject: [PATCH 35/36] fix(orchestration): require archive ownership evidence --- scripts/orchestration/dispatcher.py | 1 + scripts/orchestration/execution_context.py | 10 +- scripts/orchestration/plans.py | 3 + .../test_orchestration_workflow_contracts.py | 156 ++++++++++++++---- 4 files changed, 132 insertions(+), 38 deletions(-) diff --git a/scripts/orchestration/dispatcher.py b/scripts/orchestration/dispatcher.py index 3d10055..14c200c 100644 --- a/scripts/orchestration/dispatcher.py +++ b/scripts/orchestration/dispatcher.py @@ -168,6 +168,7 @@ def build_parser() -> argparse.ArgumentParser: set_plan.set_defaults(func=cmd_set_plan_status) archive_plan = sub.add_parser("archive-plan", parents=[parent]) archive_plan.add_argument("--id", required=True) + _add_acceptance_runtime_inputs(archive_plan) archive_plan.set_defaults(func=cmd_archive_plan) sub.add_parser("index-plans", parents=[parent]).set_defaults(func=cmd_index_plans) write_phase = sub.add_parser("write-phase", parents=[parent]) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index e5e893b..17b1065 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -73,6 +73,10 @@ } +class AcceptanceOwnershipError(SystemExit): + """A completed result failed mandatory harness-owned ownership evidence.""" + + class _SemanticReference(str): """Legacy in-memory semantic view that serializes as its stable ID.""" @@ -3228,12 +3232,12 @@ def validate_executor_result_for_task( task_ownership = None if state == "completed": if mutation_events is None: - raise SystemExit( + raise AcceptanceOwnershipError( "review-blocked: completed task requires harness-owned mutation_events evidence" ) runtime_mutation_events = list(mutation_events) if any(not isinstance(event, Mapping) for event in runtime_mutation_events): - raise SystemExit("Runtime mutation_events must contain mappings") + raise AcceptanceOwnershipError("Runtime mutation_events must contain mappings") fit = handoff.get("task_fit_check") if isinstance(handoff.get("task_fit_check"), dict) else {} operation = "repair" if fit.get("result") == "repaired" else "implementation" try: @@ -3258,7 +3262,7 @@ def validate_executor_result_for_task( authorized_replacements=authorized_replacements, ) except OwnershipBlocker as error: - raise SystemExit(str(error)) from error + raise AcceptanceOwnershipError(str(error)) from error return { "knowledge_disposition": knowledge_disposition, "unresolved": unresolved, diff --git a/scripts/orchestration/plans.py b/scripts/orchestration/plans.py index ebc5b0d..21cdaef 100644 --- a/scripts/orchestration/plans.py +++ b/scripts/orchestration/plans.py @@ -5,6 +5,7 @@ from core import * from core import _member_roots from execution_context import ( + AcceptanceOwnershipError, cmd_validate_executor_result, evaluate_knowledge_closure_state, read_structured_artifact, @@ -124,6 +125,8 @@ def _try_validate_task_handoff( observe=capability.get("result") == "mapped", **_observation_kwargs(args), ) + except AcceptanceOwnershipError: + raise except SystemExit: return None return handoff, brief diff --git a/tests/test_orchestration_workflow_contracts.py b/tests/test_orchestration_workflow_contracts.py index c44eb47..b4db039 100644 --- a/tests/test_orchestration_workflow_contracts.py +++ b/tests/test_orchestration_workflow_contracts.py @@ -59,6 +59,16 @@ def handoff_args(tmp_path: Path, **overrides: object) -> argparse.Namespace: return argparse.Namespace(**values) +def archive_args(project_root: Path, plan_id: str, **overrides: object) -> argparse.Namespace: + values: dict[str, object] = { + "project_root": str(project_root), + "id": plan_id, + "mutation_events": [], + } + values.update(overrides) + return argparse.Namespace(**values) + + def _task_brief(*, plan_id: str = "plan-001", task_id: str = "task-001") -> dict[str, object]: return { "task_id": task_id, @@ -452,11 +462,81 @@ def test_archive_plan_uses_accepted_execution_dispositions_as_knowledge_gate(tmp ) with pytest.raises(SystemExit, match="knowledge-blocked"): + cmd_archive_plan(archive_args(root, "plan-001")) + + assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() + + +def test_archive_plan_completed_handoff_rejects_missing_harness_mutation_evidence( + tmp_path: Path, +) -> None: + from plans import cmd_archive_plan + from test_orchestration_execution_context import workspace, write_executor_handoff + + root, _, _ = workspace(tmp_path) + _append_plan_knowledge(root, closure_return="missing") + write_executor_handoff( + root, + " action: none\n" + " reason: No stable authority changed.\n" + " affected_authority: []\n", + ) + + with pytest.raises(SystemExit, match="mutation.*evidence|mutation_events"): cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() +def test_archive_plan_cli_accepts_explicit_harness_mutation_evidence() -> None: + from dispatcher import build_parser + + parsed = build_parser().parse_args( + [ + "archive-plan", + "--id", + "plan-001", + "--mutation-events", + "[]", + ] + ) + + assert parsed.mutation_events == [] + + +def test_archive_plan_rejects_controller_task_scope_mutation_evidence( + tmp_path: Path, +) -> None: + from plans import cmd_archive_plan + from test_orchestration_execution_context import ( + WRITE_SCOPE_FILE, + workspace, + write_executor_handoff, + ) + + root, _, _ = workspace(tmp_path) + _append_plan_knowledge(root, closure_return="missing") + write_executor_handoff( + root, + " action: none\n" + " reason: No stable authority changed.\n" + " affected_authority: []\n", + ) + + with pytest.raises(SystemExit, match="controller mutated task-owned implementation scope"): + cmd_archive_plan( + archive_args( + root, + "plan-001", + mutation_events=[ + {"actor_kind": "controller", "paths": [WRITE_SCOPE_FILE]} + ], + ) + ) + + assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() + + @pytest.mark.parametrize( ("verdict", "action", "closure_return"), [ @@ -492,7 +572,7 @@ def test_archive_plan_allows_only_resolved_or_non_triggering_dispositions( encoding="utf-8", ) - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-001")) + cmd_archive_plan(archive_args(tmp_path, "plan-001")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan.md").is_file() @@ -705,7 +785,7 @@ def test_archive_plan_ignores_foreign_plan_handoff_with_colliding_task_id(tmp_pa _write_archive_plan(tmp_path, "plan-B") _write_archive_handoff(tmp_path, "plan-a.yaml", "{plan: plan-A, task: task-001}") - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan-B.md").is_file() assert (tmp_path / ".work-bundle/orchestration/plan/active/plan-A.md").is_file() @@ -737,7 +817,7 @@ def test_archive_plan_skips_unrelated_unparseable_executor_yaml(tmp_path: Path) encoding="utf-8", ) - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan-B.md").is_file() assert (tmp_path / ".work-bundle/orchestration/plan/active/plan-A.md").is_file() @@ -749,7 +829,7 @@ def test_archive_plan_ignores_task_only_handoff_with_ambiguous_task_id(tmp_path: _write_archive_plan(tmp_path, "plan-B") _write_archive_handoff(tmp_path, "task-only.yaml", "{task: task-001}") - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan-B.md").is_file() @@ -763,7 +843,7 @@ def test_archive_plan_ignores_archived_foreign_handoff_with_colliding_task_id(tm tmp_path, "historical.yaml", "{plan: plan-A, task: task-001}", location="archived" ) - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan-B.md").is_file() @@ -780,7 +860,7 @@ def test_archive_plan_same_plan_accepted_update_still_blocks_unresolved_closure( ) with pytest.raises(SystemExit, match="knowledge-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() @@ -791,7 +871,7 @@ def test_archive_plan_same_plan_resolved_closure_allows_archive(tmp_path: Path) _write_archive_plan(tmp_path, "plan-B", closure_return="completed") _write_archive_handoff(tmp_path, "plan-b.yaml", "{plan: plan-B, task: task-001}") - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan-B.md").is_file() @@ -986,7 +1066,9 @@ def test_no_review_completed_handoff_does_not_require_accept_or_reviewer() -> No assert token in execute assert "verdict: accept" not in str(_completed_executor_result()) - validated = validate_executor_result_for_task(_completed_executor_result(), _task_brief()) + validated = validate_executor_result_for_task( + _completed_executor_result(), _task_brief(), mutation_events=[] + ) assert validated["result_state"] == "completed" assert validated["knowledge_disposition"]["action"] == "none" @@ -1097,7 +1179,7 @@ def test_failing_declared_plan_acceptance_blocks_archive_without_second_reviewer ) with pytest.raises(SystemExit, match="acceptance-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/active/plan-B.md").is_file() @@ -1161,7 +1243,7 @@ def test_archive_plan_accepts_mapped_invariant_handoff_with_harness_observation( root, _binding, _command = _mapped_archive_workspace(tmp_path) - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1172,9 +1254,9 @@ def test_archive_plan_forwards_execution_binding_into_task_revalidation(tmp_path root, binding, command = _mapped_archive_workspace(tmp_path) cmd_archive_plan( - argparse.Namespace( - project_root=str(root), - id="plan-001", + archive_args( + root, + "plan-001", workspace_id=binding["workspace_id"], execution_id=binding["execution_id"], repository_id=binding["repository_id"], @@ -1188,9 +1270,9 @@ def test_archive_plan_forwards_execution_binding_into_task_revalidation(tmp_path restored_root, restored_binding, _command = _mapped_archive_workspace(restored) with pytest.raises(SystemExit, match=rf"acceptance-blocked: declared plan-level acceptance {command} is missing"): cmd_archive_plan( - argparse.Namespace( - project_root=str(restored_root), - id="plan-001", + archive_args( + restored_root, + "plan-001", workspace_id="wrong-workspace", execution_id=restored_binding["execution_id"], repository_id=restored_binding["repository_id"], @@ -1236,7 +1318,7 @@ def test_passing_declared_plan_acceptance_allows_archive_without_second_reviewer encoding="utf-8", ) - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1273,7 +1355,7 @@ def test_unvalidated_handoff_cannot_satisfy_declared_plan_acceptance(tmp_path: P ) with pytest.raises(SystemExit, match="acceptance-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) def _record_tree_fresh_integration_pass(root: Path, command: str) -> str: @@ -1306,7 +1388,7 @@ def test_tree_fresh_executor_pass_without_harness_observation_blocks_archive(tmp _record_tree_fresh_integration_pass(root, command) with pytest.raises(SystemExit, match="acceptance-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() @@ -1327,7 +1409,7 @@ def test_task_worktree_command_pass_cannot_archive_different_final_workspace(tmp _write_earlier_integration_pass(root, command, created_at="2026-08-17") with pytest.raises(SystemExit, match="acceptance-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() assert not (root / "worktree-only-marker").exists() @@ -1344,7 +1426,7 @@ def test_passing_mutating_integration_command_blocks_archive(tmp_path: Path) -> _record_tree_fresh_integration_pass(root, command) with pytest.raises(SystemExit, match="acceptance-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() @@ -1390,7 +1472,7 @@ def test_contradictory_validated_plan_acceptance_blocks_archive(tmp_path: Path) ) with pytest.raises(SystemExit, match="acceptance-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) def test_stale_plan_acceptance_after_later_material_task_blocks_archive(tmp_path: Path) -> None: @@ -1417,7 +1499,7 @@ def test_stale_plan_acceptance_after_later_material_task_blocks_archive(tmp_path ) with pytest.raises(SystemExit, match="acceptance-blocked:.*is stale"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() @@ -1447,7 +1529,7 @@ def test_fresh_plan_acceptance_rerun_after_later_task_allows_archive(tmp_path: P actual_commit=later, ) - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1462,7 +1544,7 @@ def test_archive_moves_plan_directory_named_for_root_artifact(tmp_path: Path) -> (active / "compiler-plan.md").rename(active / "plan-001-feature.md") (active / "plan-001").rename(active / "plan-001-feature") - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) archived = root / ".work-bundle/orchestration/plan/archived" assert (archived / "plan-001-feature.md").is_file() @@ -1483,7 +1565,7 @@ def test_archive_reconciles_archived_root_with_active_plan_directory(tmp_path: P (active / "compiler-plan.md").rename(archived / "plan-001-feature.md") (active / "plan-001").rename(active / "plan-001-feature") - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (archived / "plan-001-feature.md").is_file() assert (archived / "plan-001-feature").is_dir() @@ -1523,7 +1605,7 @@ def test_same_day_out_of_id_order_stale_plan_acceptance_blocks_archive(tmp_path: ) with pytest.raises(SystemExit, match="acceptance-blocked:.*is stale"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() @@ -1561,7 +1643,7 @@ def test_same_day_out_of_id_order_fresh_rerun_allows_archive(tmp_path: Path) -> actual_commit=later, ) - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1600,7 +1682,7 @@ def test_historical_failed_plan_acceptance_does_not_poison_fresh_head_pass(tmp_p actual_commit=later, ) - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1634,7 +1716,7 @@ def test_same_tree_contradictory_plan_acceptance_still_blocks_archive(tmp_path: ) with pytest.raises(SystemExit, match="acceptance-blocked:.*contradictory"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) def test_precommit_tree_pass_survives_same_tree_finalization_commit(tmp_path: Path) -> None: @@ -1667,7 +1749,7 @@ def test_precommit_tree_pass_survives_same_tree_finalization_commit(tmp_path: Pa ) git(root, "commit", "-qm", "finalize") - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1686,7 +1768,7 @@ def test_archive_plan_no_review_completed_update_blocks_until_closure_return( ) with pytest.raises(SystemExit, match="knowledge-blocked"): - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/active/compiler-plan.md").is_file() @@ -1703,7 +1785,7 @@ def test_archive_plan_ignores_unvalidated_update_handoff(tmp_path: Path) -> None result_state="completed", ) - cmd_archive_plan(argparse.Namespace(project_root=str(tmp_path), id="plan-B")) + cmd_archive_plan(archive_args(tmp_path, "plan-B")) assert (tmp_path / ".work-bundle/orchestration/plan/archived/plan-B.md").is_file() @@ -1726,7 +1808,7 @@ def test_archive_plan_review_required_cannot_downgrade_via_omitted_required(tmp_ f" action: update\n reason: Task-local evidence.\n affected_authority: [{ACCEPTED_AUTHORITY}]\n", ) - cmd_archive_plan(argparse.Namespace(project_root=str(root), id="plan-001")) + cmd_archive_plan(archive_args(root, "plan-001")) assert (root / ".work-bundle/orchestration/plan/archived/compiler-plan.md").is_file() @@ -1785,7 +1867,11 @@ def test_review_required_task_fails_closed_until_independent_accept() -> None: accepted = _completed_executor_result( acceptance_review={"required": True, "verdict": "accept"} ) - validated = validate_executor_result_for_task(accepted, {**_task_brief(), "review_required": True}) + validated = validate_executor_result_for_task( + accepted, + {**_task_brief(), "review_required": True}, + mutation_events=[], + ) assert validated["result_state"] == "completed" From fdd5940d63634eb3c94165ac3948bf5c397c08c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 6 Sep 2026 14:09:04 +0800 Subject: [PATCH 36/36] test(orchestration): refresh WOR-108 migration impact --- evals/wor108/migration-impact.json | 1 + tests/test_wor108_closure.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/evals/wor108/migration-impact.json b/evals/wor108/migration-impact.json index 6e5e2cc..e12b3a8 100644 --- a/evals/wor108/migration-impact.json +++ b/evals/wor108/migration-impact.json @@ -27,6 +27,7 @@ "operations": [ "scripts/orchestration/dispatcher.py", "scripts/orchestration/execution_context.py", + "scripts/orchestration/plans.py", "scripts/orchestration/review_runtime.py", "scripts/orchestration/task_ownership.py", "scripts/work-bundle/control_plane.py", diff --git a/tests/test_wor108_closure.py b/tests/test_wor108_closure.py index 67e4213..4eba67a 100644 --- a/tests/test_wor108_closure.py +++ b/tests/test_wor108_closure.py @@ -21,7 +21,7 @@ def test_wor108_closure_registry_has_exact_public_fixture_identities() -> None: assert result == { "evaluation_id": "wor108-legacy-closure-v1", "fixtures": 22, - "changed_surfaces": 43, + "changed_surfaces": 44, "verdict": "accepted", }