From 63558d3558db6e078a66612c1d79211174bbde36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:19:45 +0800 Subject: [PATCH 1/9] feat(orchestration): compile evidence capability maps --- .../assets/orchestration/contract/plan-v1.md | 4 ++ .../assets/orchestration/contract/task-v1.md | 5 ++ references/assets/orchestration/workflow.md | 2 + references/evals/orchestration/evals.json | 18 +++++++ scripts/orchestration/execution_context.py | 54 +++++++++++++++++++ .../orch-create-implementation-plan/SKILL.md | 5 +- tests/test_orchestration_execution_context.py | 13 +++++ .../test_orchestration_workflow_contracts.py | 17 ++++++ 8 files changed, 117 insertions(+), 1 deletion(-) diff --git a/references/assets/orchestration/contract/plan-v1.md b/references/assets/orchestration/contract/plan-v1.md index 637e495..3853842 100644 --- a/references/assets/orchestration/contract/plan-v1.md +++ b/references/assets/orchestration/contract/plan-v1.md @@ -35,6 +35,10 @@ allocated_skills: applies_when: [observable plan-wide condition] use_timing: planning|task_execution|review required_for: [why executors need this skill context] +evidence_capability: + result: mapped | no_validation_bearing_obligation + reason: [non-empty reason] + invariants: [stable per-invariant capability entries allocated to task IDs] --- # Implementation Plan: [Plan Goal] diff --git a/references/assets/orchestration/contract/task-v1.md b/references/assets/orchestration/contract/task-v1.md index f252518..5ee14f9 100644 --- a/references/assets/orchestration/contract/task-v1.md +++ b/references/assets/orchestration/contract/task-v1.md @@ -66,6 +66,11 @@ validation: mechanism: named-harness-owned-mechanism proves: [claim] expected: passed +evidence_capability: + result: mapped | no_validation_bearing_obligation + reason: [non-empty reason] + invariants: + - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | integration | runtime | ui_visual | inspection | other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001]} --- # TASK-001: [Task Name] diff --git a/references/assets/orchestration/workflow.md b/references/assets/orchestration/workflow.md index 286b880..93d90ee 100644 --- a/references/assets/orchestration/workflow.md +++ b/references/assets/orchestration/workflow.md @@ -118,6 +118,8 @@ Specification authoring materializes `impact_decisions` from bounded current-sta Within existing Design Interrogation, specification authoring also records one `excellence_applicability` result after one compact pass: `no_material_opportunity` with an evidence-backed reason, or `material_opportunities` with proposals selected from task evidence and change shape rather than a universal checklist. Surface an option only when accepting or rejecting it could change a requirement, constraint, acceptance criterion, user-observable or contractual outcome, architectural boundary, measurable quality target, validation target, or declared boundary. Each proposal records user value, evidence, cost, risk, recommendation, and `accepted | rejected | deferred | not_material`; unanswered proposals become deferred. Only accepted proposals may project through stable IDs into authoritative requirements, constraints, interfaces, acceptance criteria, or validation targets. Other proposals remain traceable but excluded from planning, executor briefs, and acceptance obligations. The pass stops when further exploration could change none of those surfaces, records the reason, and ensures every surfaced proposal has a disposition. It does not add a lifecycle stage, force a recommendation, or make optional proposals blocking unless accepted projection is incomplete or an unresolved safety or authority conflict exists. The excellence-applicability view is compared by semantic convergence, while agent judgment owns opportunity materiality and recommendation quality. +Planning allocates every accepted validation-bearing obligation or design decision to stable `evidence_capability` entries before execution. Each entry names `source_ids`, invariant, boundary, oracle, `capability_reason`, `freshness`, `task_id`, and task-local `evidence_ids`; task briefs and review packages compile only the owning task's entries. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation exists, never from a WOR-61 `none_relevant` impact result alone. Select the lightest capable boundary per invariant rather than imposing universal runtime, browser, visual, performance, or E2E proof. Mechanical helpers validate IDs, completeness, task ownership, and provenance; agents own semantic capability judgment. + ## Lightweight development lane Use `dev-create-task-plan` for bounded mechanical work with stable decisions. After preflight and source grounding it invokes one bounded `ks-what-is-helpful` gateway, carries accepted authority or evidence-backed `none relevant`, writes one disposable plan under `.work-bundle/runtime/dev-plans/`, and creates no orchestration artifact tree. Its lightweight completion owner records an evidence-backed no-write result for `none`; for `update`, `supersede`, or `reclassify`, it invokes the approved keep-summarizing lifecycle and validates return evidence before completion. Escalate to full orchestration for unresolved architecture/API/data/workflow decisions, wide impact, multiple repositories, migration/deployment sequencing, unresolved durable-knowledge decisions, or parallel contract/barrier needs. diff --git a/references/evals/orchestration/evals.json b/references/evals/orchestration/evals.json index 2f79311..fc457c1 100644 --- a/references/evals/orchestration/evals.json +++ b/references/evals/orchestration/evals.json @@ -492,6 +492,24 @@ "prompt": "An excellence scan finds one adjacent idea that cannot change an accepted contract surface and another option whose acceptance could change a user-observable outcome and validation target.", "expected_output": "Omits the related-but-non-material adjacent idea or records it not_material, surfaces the outcome-changing option as a proposal because accepting or rejecting it could change a requirement, constraint, acceptance criterion, user-observable or contractual outcome, architectural boundary, measurable quality target, validation target, or declared boundary, and stops when further exploration can change none of those surfaces with the reason recorded.", "files": [] + }, + { + "id": 89, + "prompt": "Compile a task with an accepted invariant whose evidence-capability entry cites stable source IDs and a task-local validation oracle.", + "expected_output": "Compiles the task-local evidence_capability entry with invariant ID, source IDs, boundary, oracle, capability_reason, freshness, task owner, and evidence IDs without executor retrieval.", + "files": [] + }, + { + "id": 90, + "prompt": "A greenfield task records WOR-61 none_relevant but still has an accepted validation-bearing requirement and claims no_validation_bearing_obligation.", + "expected_output": "Rejects the empty evidence-capability map because none_relevant impact does not prove absence of accepted validation-bearing obligations.", + "files": [] + }, + { + "id": 91, + "prompt": "A plan maps a presentation invariant to a unit oracle that cannot observe rendering, while a lighter capable visual inspection is available.", + "expected_output": "Rejects the incapable unit mapping and allocates the lightest capable evidence boundary for the named invariant; it does not impose a universal UI gate on unrelated invariants.", + "files": [] } ], "v4_evals": [ diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 81b478e..cedf115 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -39,6 +39,8 @@ "conflict_status", ) KNOWLEDGE_DISPOSITION_ACTIONS = {"none", "update", "supersede", "reclassify"} +EVIDENCE_CAPABILITY_RESULTS = {"mapped", "no_validation_bearing_obligation"} +EVIDENCE_BOUNDARIES = {"unit", "integration", "runtime", "ui_visual", "inspection", "other"} KNOWLEDGE_PERSISTENCE_INSTRUCTION_RE = re.compile( r"(?:\.work-bundle/knowledge(?:/|\b)|\bks-[a-z0-9-]+\b)", re.IGNORECASE, @@ -658,6 +660,56 @@ def _compile_truth_basis( } +def _compile_evidence_capability( + task: dict[str, Any], task_id: str, source_ids: list[str], validation: list[dict[str, Any]] +) -> dict[str, Any] | None: + raw = task.get("evidence_capability") + if raw is None: + return None + if not isinstance(raw, dict) or raw.get("result") not in EVIDENCE_CAPABILITY_RESULTS: + raise SystemExit("Task evidence_capability result must be mapped or no_validation_bearing_obligation") + reason = _nonempty_text(raw.get("reason")) + if reason is None: + raise SystemExit("Task evidence_capability reason must be non-empty") + invariants = [item for item in _as_list(raw.get("invariants")) if isinstance(item, dict)] + if raw["result"] == "no_validation_bearing_obligation": + if invariants: + raise SystemExit("no_validation_bearing_obligation requires an empty invariant map") + if any(identifier.startswith(("REQ-", "AC-")) for identifier in source_ids): + raise SystemExit("no_validation_bearing_obligation cannot be inferred while accepted requirement or acceptance IDs remain") + return {"result": raw["result"], "reason": reason, "invariants": []} + if not invariants: + raise SystemExit("mapped evidence_capability requires at least one invariant") + validation_by_id = {str(item.get("id")): item for item in validation if _nonempty_text(item.get("id"))} + compiled: list[dict[str, Any]] = [] + seen: set[str] = set() + for item in invariants: + invariant_id = _nonempty_text(item.get("id")) + if invariant_id is None or invariant_id in seen: + raise SystemExit("Evidence capability invariant IDs must be stable and unique") + seen.add(invariant_id) + allocated_sources = [str(value) for value in _as_list(item.get("source_ids"))] + if not allocated_sources or any(value not in source_ids for value in allocated_sources): + raise SystemExit(f"Evidence capability {invariant_id} cites unallocated source IDs") + if item.get("boundary") not in EVIDENCE_BOUNDARIES: + raise SystemExit(f"Evidence capability {invariant_id} has an invalid boundary") + if item.get("task_id") != task_id: + raise SystemExit(f"Evidence capability {invariant_id} has the wrong task owner") + evidence_ids = [str(value) for value in _as_list(item.get("evidence_ids"))] + if not evidence_ids or any(value not in validation_by_id for value in evidence_ids): + raise SystemExit(f"Evidence capability {invariant_id} cites missing validation evidence") + for field in ("invariant", "oracle", "capability_reason", "freshness"): + if _nonempty_text(item.get(field)) is None: + raise SystemExit(f"Evidence capability {invariant_id} missing {field}") + for evidence_id in evidence_ids: + if invariant_id not in _as_list(validation_by_id[evidence_id].get("invariant_ids")): + raise SystemExit(f"Validation {evidence_id} does not bind {invariant_id}") + if _nonempty_text(validation_by_id[evidence_id].get("capability_reason")) is None: + raise SystemExit(f"Validation {evidence_id} missing capability_reason") + compiled.append(dict(item)) + return {"result": "mapped", "reason": reason, "invariants": compiled} + + def evaluate_knowledge_closure_state( *, upstream_disposition: str, @@ -1677,6 +1729,7 @@ def _compile_task_brief(args: argparse.Namespace) -> tuple[Path, dict[str, Any]] } truth_basis = _compile_truth_basis(task, records, source_paths) validation = _resolve_reference(validation_value, records, source_paths) + evidence_capability = _compile_evidence_capability(task, task_id, source_ids, validation) acceptance_review = task.get("acceptance_review") if acceptance_review in (None, {}): review_required = False @@ -1706,6 +1759,7 @@ def _compile_task_brief(args: argparse.Namespace) -> tuple[Path, dict[str, Any]] "evidence_applicability": evidence_applicability, "workspace": {"root": str(root)}, "validation": validation, + "evidence_capability": evidence_capability, "handoff_contract": "executor-result-v1", "review_required": review_required, } diff --git a/skills/orch-create-implementation-plan/SKILL.md b/skills/orch-create-implementation-plan/SKILL.md index 34c84b2..884d248 100644 --- a/skills/orch-create-implementation-plan/SKILL.md +++ b/skills/orch-create-implementation-plan/SKILL.md @@ -13,7 +13,7 @@ Plan only from a verified active specification with converged semantics, resolve 1. Use the specification and bounded repository evidence. Add upstream/downstream or validation scope only when current evidence proves it. 2. Use the minimum orchestration overhead that preserves Truth Basis continuity, independently falsifiable and testable increments, short evidence loops, exact dependencies, disjoint write scopes, validation ownership, bounded failure radius, and review boundaries. Do not split one mechanical increment when it already satisfies those constraints. -3. Give every task exact source IDs, a five-field Truth Basis, scope, interfaces, dependencies, steps, evidence, methodology, allocated rules/skills, executor profile, and review requirement. +3. Give every task exact source IDs, a five-field Truth Basis, scope, interfaces, dependencies, steps, evidence, methodology, allocated rules/skills, executor profile, and review requirement. Allocate every accepted validation-bearing obligation to a stable `evidence_capability` invariant and the lightest capable task-local oracle. Each entry records source IDs, boundary, oracle, capability reason, freshness, task owner, and validation evidence IDs. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation or design decision exists; never infer it from WOR-61 `none_relevant`. 4. Carry execution-workspace isolation, hydration, and cleanup policy into task and executor context; mutating siblings on the same execution path isolate via prepare_worktree or serialize even when write scopes are disjoint. 5. Use a common contract group before safe parallel work. Contract-decoupled participants depend on the common contract group and accepted prior handoffs, not sibling in-progress implementation output. Create explicit barrier metadata with barrier ID, readiness evidence, and convergence owner. Cross-branch or joint validation belongs to a post-barrier convergence task. 6. Require a compact `executor-result-v1` handoff. Default `acceptance_review.required: false`. Require task review only when the task sets `acceptance_review.required: true`. Do not infer that flag from soft applicability prose. @@ -68,6 +68,8 @@ truth_basis: The compiler resolves each allocated `AUTH-NNN` alias to `AUTH-NNN: ` from verified specification `source_knowledge` without exposing knowledge paths. +Each mapped invariant carries `source_ids`, `boundary`, `oracle`, `capability_reason`, `freshness`, `task_id`, and `evidence_ids`. Task validation entries carry stable `id`, `invariant_ids`, and their own `capability_reason`. + ## Semantic convergence Use `dev-semantic-convergence` with these lenses: @@ -78,6 +80,7 @@ Use `dev-semantic-convergence` with these lenses: - rule, skill, and methodology allocation; - parallel barrier and convergence safety; - executor-context completeness. +- evidence-capability completeness, stable source projection, task-local filtering, and lightest-capable boundary selection. Repair generated drift in the same turn and record compact `semantic_loop` result, round count, and repaired defects. If a source requirement or decision is missing, stop for specification repair. diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 3fcaed0..334f782 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -19,6 +19,19 @@ from execution_context import build_review_package, build_task_brief # noqa: E402 +def test_compile_evidence_capability_maps_stable_task_local_invariants() -> None: + validation = [{"id": "VAL-001", "invariant_ids": ["INV-001"], "capability_reason": "Observes violation."}] + task = {"evidence_capability": {"result": "mapped", "reason": "Required.", "invariants": [{"id": "INV-001", "source_ids": ["REQ-001"], "invariant": "Observable behavior", "boundary": "unit", "oracle": "VAL-001", "capability_reason": "Unit oracle distinguishes violation.", "freshness": "current_task_batch", "task_id": "task-001", "evidence_ids": ["VAL-001"]}]}} + result = execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], validation) + assert result is not None and result["invariants"][0]["id"] == "INV-001" + + +def test_compile_evidence_capability_rejects_greenfield_escape() -> None: + task = {"evidence_capability": {"result": "no_validation_bearing_obligation", "reason": "Impact was none_relevant.", "invariants": []}} + with pytest.raises(SystemExit, match="cannot be inferred"): + execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], []) + + ACCEPTED_AUTHORITY_PATH = ".work-bundle/knowledge/notes/accepted-authority.md" ACCEPTED_AUTHORITY = "AUTH-001" ACCEPTED_CONSTRAINT = "Executors must not retrieve durable knowledge to reconstruct authority." diff --git a/tests/test_orchestration_workflow_contracts.py b/tests/test_orchestration_workflow_contracts.py index 3171434..060cabd 100644 --- a/tests/test_orchestration_workflow_contracts.py +++ b/tests/test_orchestration_workflow_contracts.py @@ -397,6 +397,23 @@ def test_specification_contract_requires_bounded_excellence_applicability() -> N assert "related-but-non-material adjacent idea" in evals +def test_planning_contract_allocates_evidence_capability() -> None: + plan = read("references/assets/orchestration/contract/plan-v1.md") + task = read("references/assets/orchestration/contract/task-v1.md") + skill = read("skills/orch-create-implementation-plan/SKILL.md") + workflow = read("references/assets/orchestration/workflow.md") + for text in (plan, task, skill, workflow): + assert "evidence_capability" in text + assert "no_validation_bearing_obligation" in text + for text in (task, skill, workflow): + assert "capability_reason" in text + assert "freshness" in text + assert "task" in text.lower() + assert "WOR-61 `none_relevant`" in skill + assert "lightest capable" in skill + assert "universal runtime" in workflow + + def test_archive_plan_uses_accepted_execution_dispositions_as_knowledge_gate(tmp_path: Path) -> None: from plans import cmd_archive_plan from test_orchestration_execution_context import ACCEPTED_AUTHORITY, workspace, write_executor_handoff From 30e349735f400673c418f27fdf89465ca91caf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:21:00 +0800 Subject: [PATCH 2/9] fix(orchestration): preserve capability evidence IDs --- scripts/orchestration/execution_context.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index cedf115..578ca28 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1614,7 +1614,7 @@ def _compile_structured_validation_item(item: Any) -> dict[str, Any]: "Task validation kind must be process or inspection; untyped structured validation is legacy-untyped" ) compiled["kind"] = kind - for key in ("command", "proves", "expected", "acceptable_results", "digest"): + for key in ("id", "invariant_ids", "capability_reason", "command", "proves", "expected", "acceptable_results", "digest"): if key in item: compiled[key] = item[key] if kind == "inspection": @@ -1728,7 +1728,13 @@ def _compile_task_brief(args: argparse.Namespace) -> tuple[Path, dict[str, Any]] "constraints": [f"{sid}: {records[sid]}" for sid in source_ids if sid.startswith("CON-")], } truth_basis = _compile_truth_basis(task, records, source_paths) - validation = _resolve_reference(validation_value, records, source_paths) + validation = [ + { + key: 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 + ] evidence_capability = _compile_evidence_capability(task, task_id, source_ids, validation) acceptance_review = task.get("acceptance_review") if acceptance_review in (None, {}): From a3a56411ceadb8f66a4233c84a44727576168fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:28:11 +0800 Subject: [PATCH 3/9] fix(orchestration): tighten evidence capability authority --- .../assets/orchestration/contract/task-v1.md | 2 +- scripts/orchestration/execution_context.py | 13 +++++++++---- tests/test_orchestration_execution_context.py | 17 +++++++++++++---- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/references/assets/orchestration/contract/task-v1.md b/references/assets/orchestration/contract/task-v1.md index 5ee14f9..433596e 100644 --- a/references/assets/orchestration/contract/task-v1.md +++ b/references/assets/orchestration/contract/task-v1.md @@ -70,7 +70,7 @@ evidence_capability: result: mapped | no_validation_bearing_obligation reason: [non-empty reason] invariants: - - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | integration | runtime | ui_visual | inspection | other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001]} + - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | component | integration | runtime | ui_visual | performance | accessibility | inspection | other, other_mechanism: required-when-other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001]} --- # TASK-001: [Task Name] diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 578ca28..6aa60a7 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -40,7 +40,7 @@ ) KNOWLEDGE_DISPOSITION_ACTIONS = {"none", "update", "supersede", "reclassify"} EVIDENCE_CAPABILITY_RESULTS = {"mapped", "no_validation_bearing_obligation"} -EVIDENCE_BOUNDARIES = {"unit", "integration", "runtime", "ui_visual", "inspection", "other"} +EVIDENCE_BOUNDARIES = {"unit", "component", "integration", "runtime", "ui_visual", "performance", "accessibility", "inspection", "other"} KNOWLEDGE_PERSISTENCE_INSTRUCTION_RE = re.compile( r"(?:\.work-bundle/knowledge(?:/|\b)|\bks-[a-z0-9-]+\b)", re.IGNORECASE, @@ -665,7 +665,7 @@ def _compile_evidence_capability( ) -> dict[str, Any] | None: raw = task.get("evidence_capability") if raw is None: - return None + raise SystemExit("Task evidence_capability is required") if not isinstance(raw, dict) or raw.get("result") not in EVIDENCE_CAPABILITY_RESULTS: raise SystemExit("Task evidence_capability result must be mapped or no_validation_bearing_obligation") reason = _nonempty_text(raw.get("reason")) @@ -675,8 +675,6 @@ def _compile_evidence_capability( if raw["result"] == "no_validation_bearing_obligation": if invariants: raise SystemExit("no_validation_bearing_obligation requires an empty invariant map") - if any(identifier.startswith(("REQ-", "AC-")) for identifier in source_ids): - raise SystemExit("no_validation_bearing_obligation cannot be inferred while accepted requirement or acceptance IDs remain") return {"result": raw["result"], "reason": reason, "invariants": []} if not invariants: raise SystemExit("mapped evidence_capability requires at least one invariant") @@ -693,6 +691,8 @@ def _compile_evidence_capability( raise SystemExit(f"Evidence capability {invariant_id} cites unallocated source IDs") if item.get("boundary") not in EVIDENCE_BOUNDARIES: raise SystemExit(f"Evidence capability {invariant_id} has an invalid boundary") + if item.get("boundary") == "other" and _nonempty_text(item.get("other_mechanism")) is None: + raise SystemExit(f"Evidence capability {invariant_id} other boundary requires other_mechanism") if item.get("task_id") != task_id: raise SystemExit(f"Evidence capability {invariant_id} has the wrong task owner") evidence_ids = [str(value) for value in _as_list(item.get("evidence_ids"))] @@ -701,6 +701,8 @@ def _compile_evidence_capability( for field in ("invariant", "oracle", "capability_reason", "freshness"): if _nonempty_text(item.get(field)) is None: raise SystemExit(f"Evidence capability {invariant_id} missing {field}") + if str(item.get("oracle")) not in evidence_ids: + raise SystemExit(f"Evidence capability {invariant_id} oracle must name an allocated evidence ID") for evidence_id in evidence_ids: if invariant_id not in _as_list(validation_by_id[evidence_id].get("invariant_ids")): raise SystemExit(f"Validation {evidence_id} does not bind {invariant_id}") @@ -1991,6 +1993,9 @@ def build_review_package(args: argparse.Namespace) -> Path: "## Accepted Truth Basis", *_markdown_items([task.get("truth_basis", {})]), "", + "## Evidence capability", + *_markdown_items([task.get("evidence_capability", {})]), + "", "## Allowed scope", *_markdown_items(allowed_scope), "", diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 334f782..e6f3809 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -26,10 +26,15 @@ def test_compile_evidence_capability_maps_stable_task_local_invariants() -> None assert result is not None and result["invariants"][0]["id"] == "INV-001" -def test_compile_evidence_capability_rejects_greenfield_escape() -> None: - task = {"evidence_capability": {"result": "no_validation_bearing_obligation", "reason": "Impact was none_relevant.", "invariants": []}} - with pytest.raises(SystemExit, match="cannot be inferred"): - execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], []) +def test_compile_evidence_capability_requires_explicit_result() -> None: + with pytest.raises(SystemExit, match="required"): + execution_context._compile_evidence_capability({}, "task-001", ["REQ-001"], []) + + +def test_compile_evidence_capability_allows_agent_decided_bookkeeping_empty_map() -> None: + task = {"evidence_capability": {"result": "no_validation_bearing_obligation", "reason": "Accepted IDs are bookkeeping-only and make no closure claim.", "invariants": []}} + result = execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], []) + assert result is not None and result["invariants"] == [] ACCEPTED_AUTHORITY_PATH = ".work-bundle/knowledge/notes/accepted-authority.md" @@ -121,6 +126,10 @@ def workspace(tmp_path: Path) -> tuple[Path, Path, Path]: " context_mode: compiled-brief\n" "acceptance_review:\n" " required: false\n" + "evidence_capability:\n" + " result: no_validation_bearing_obligation\n" + " reason: This shared fixture leaves capability semantics to scenario-specific tests.\n" + " invariants: []\n" "validation:\n" " - {kind: process, command: uv run --with pytest pytest -q tests/test_one.py, proves: TEST-004, expected: exit 0}\n" "---\n\n# Task\n", From 6b82703008fd7b2c1a590229662787a7d5d1673d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:33:30 +0800 Subject: [PATCH 4/9] fix(orchestration): initialize evidence closure state --- .../assets/orchestration/contract/plan-v1.md | 2 +- .../assets/orchestration/contract/task-v1.md | 2 +- references/assets/orchestration/workflow.md | 2 +- references/evals/orchestration/evals.json | 2 +- scripts/orchestration/execution_context.py | 3 ++ .../orch-create-implementation-plan/SKILL.md | 2 +- tests/test_orchestration_execution_context.py | 32 ++++++++++++++++++- 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/references/assets/orchestration/contract/plan-v1.md b/references/assets/orchestration/contract/plan-v1.md index 3853842..60f589d 100644 --- a/references/assets/orchestration/contract/plan-v1.md +++ b/references/assets/orchestration/contract/plan-v1.md @@ -38,7 +38,7 @@ allocated_skills: evidence_capability: result: mapped | no_validation_bearing_obligation reason: [non-empty reason] - invariants: [stable per-invariant capability entries allocated to task IDs] + invariants: [stable per-invariant capability entries allocated to task IDs with closure_result initialized to pending] --- # Implementation Plan: [Plan Goal] diff --git a/references/assets/orchestration/contract/task-v1.md b/references/assets/orchestration/contract/task-v1.md index 433596e..d69338f 100644 --- a/references/assets/orchestration/contract/task-v1.md +++ b/references/assets/orchestration/contract/task-v1.md @@ -70,7 +70,7 @@ evidence_capability: result: mapped | no_validation_bearing_obligation reason: [non-empty reason] invariants: - - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | component | integration | runtime | ui_visual | performance | accessibility | inspection | other, other_mechanism: required-when-other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001]} + - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | component | integration | runtime | ui_visual | performance | accessibility | inspection | other, other_mechanism: required-when-other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001], closure_result: pending | passed | incapable | contradictory | stale | wrong_boundary | failed | missing | unexecuted} --- # TASK-001: [Task Name] diff --git a/references/assets/orchestration/workflow.md b/references/assets/orchestration/workflow.md index 93d90ee..7cd5161 100644 --- a/references/assets/orchestration/workflow.md +++ b/references/assets/orchestration/workflow.md @@ -118,7 +118,7 @@ Specification authoring materializes `impact_decisions` from bounded current-sta Within existing Design Interrogation, specification authoring also records one `excellence_applicability` result after one compact pass: `no_material_opportunity` with an evidence-backed reason, or `material_opportunities` with proposals selected from task evidence and change shape rather than a universal checklist. Surface an option only when accepting or rejecting it could change a requirement, constraint, acceptance criterion, user-observable or contractual outcome, architectural boundary, measurable quality target, validation target, or declared boundary. Each proposal records user value, evidence, cost, risk, recommendation, and `accepted | rejected | deferred | not_material`; unanswered proposals become deferred. Only accepted proposals may project through stable IDs into authoritative requirements, constraints, interfaces, acceptance criteria, or validation targets. Other proposals remain traceable but excluded from planning, executor briefs, and acceptance obligations. The pass stops when further exploration could change none of those surfaces, records the reason, and ensures every surfaced proposal has a disposition. It does not add a lifecycle stage, force a recommendation, or make optional proposals blocking unless accepted projection is incomplete or an unresolved safety or authority conflict exists. The excellence-applicability view is compared by semantic convergence, while agent judgment owns opportunity materiality and recommendation quality. -Planning allocates every accepted validation-bearing obligation or design decision to stable `evidence_capability` entries before execution. Each entry names `source_ids`, invariant, boundary, oracle, `capability_reason`, `freshness`, `task_id`, and task-local `evidence_ids`; task briefs and review packages compile only the owning task's entries. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation exists, never from a WOR-61 `none_relevant` impact result alone. Select the lightest capable boundary per invariant rather than imposing universal runtime, browser, visual, performance, or E2E proof. Mechanical helpers validate IDs, completeness, task ownership, and provenance; agents own semantic capability judgment. +Planning allocates every accepted validation-bearing obligation or design decision to stable `evidence_capability` entries before execution. Each entry names `source_ids`, invariant, boundary, oracle, `capability_reason`, `freshness`, `task_id`, task-local `evidence_ids`, and initializes `closure_result: pending`; task briefs and review packages compile only the owning task's entries. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation exists, never from a WOR-61 `none_relevant` impact result alone. Select the lightest capable boundary per invariant rather than imposing universal runtime, browser, visual, performance, or E2E proof. Mechanical helpers validate IDs, completeness, task ownership, and provenance; agents own semantic capability judgment. ## Lightweight development lane diff --git a/references/evals/orchestration/evals.json b/references/evals/orchestration/evals.json index fc457c1..5a5625d 100644 --- a/references/evals/orchestration/evals.json +++ b/references/evals/orchestration/evals.json @@ -496,7 +496,7 @@ { "id": 89, "prompt": "Compile a task with an accepted invariant whose evidence-capability entry cites stable source IDs and a task-local validation oracle.", - "expected_output": "Compiles the task-local evidence_capability entry with invariant ID, source IDs, boundary, oracle, capability_reason, freshness, task owner, and evidence IDs without executor retrieval.", + "expected_output": "Compiles the task-local evidence_capability entry with invariant ID, source IDs, boundary, oracle, capability_reason, freshness, task owner, evidence IDs, and closure_result initialized to pending without executor retrieval.", "files": [] }, { diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 6aa60a7..f83dd65 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -41,6 +41,7 @@ KNOWLEDGE_DISPOSITION_ACTIONS = {"none", "update", "supersede", "reclassify"} EVIDENCE_CAPABILITY_RESULTS = {"mapped", "no_validation_bearing_obligation"} EVIDENCE_BOUNDARIES = {"unit", "component", "integration", "runtime", "ui_visual", "performance", "accessibility", "inspection", "other"} +EVIDENCE_CLOSURE_RESULTS = {"pending", "passed", "incapable", "contradictory", "stale", "wrong_boundary", "failed", "missing", "unexecuted"} KNOWLEDGE_PERSISTENCE_INSTRUCTION_RE = re.compile( r"(?:\.work-bundle/knowledge(?:/|\b)|\bks-[a-z0-9-]+\b)", re.IGNORECASE, @@ -703,6 +704,8 @@ def _compile_evidence_capability( raise SystemExit(f"Evidence capability {invariant_id} missing {field}") if str(item.get("oracle")) not in evidence_ids: raise SystemExit(f"Evidence capability {invariant_id} oracle must name an allocated evidence ID") + if item.get("closure_result") not in EVIDENCE_CLOSURE_RESULTS: + raise SystemExit(f"Evidence capability {invariant_id} has an invalid closure_result") for evidence_id in evidence_ids: if invariant_id not in _as_list(validation_by_id[evidence_id].get("invariant_ids")): raise SystemExit(f"Validation {evidence_id} does not bind {invariant_id}") diff --git a/skills/orch-create-implementation-plan/SKILL.md b/skills/orch-create-implementation-plan/SKILL.md index 884d248..347f320 100644 --- a/skills/orch-create-implementation-plan/SKILL.md +++ b/skills/orch-create-implementation-plan/SKILL.md @@ -13,7 +13,7 @@ Plan only from a verified active specification with converged semantics, resolve 1. Use the specification and bounded repository evidence. Add upstream/downstream or validation scope only when current evidence proves it. 2. Use the minimum orchestration overhead that preserves Truth Basis continuity, independently falsifiable and testable increments, short evidence loops, exact dependencies, disjoint write scopes, validation ownership, bounded failure radius, and review boundaries. Do not split one mechanical increment when it already satisfies those constraints. -3. Give every task exact source IDs, a five-field Truth Basis, scope, interfaces, dependencies, steps, evidence, methodology, allocated rules/skills, executor profile, and review requirement. Allocate every accepted validation-bearing obligation to a stable `evidence_capability` invariant and the lightest capable task-local oracle. Each entry records source IDs, boundary, oracle, capability reason, freshness, task owner, and validation evidence IDs. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation or design decision exists; never infer it from WOR-61 `none_relevant`. +3. Give every task exact source IDs, a five-field Truth Basis, scope, interfaces, dependencies, steps, evidence, methodology, allocated rules/skills, executor profile, and review requirement. Allocate every accepted validation-bearing obligation to a stable `evidence_capability` invariant and the lightest capable task-local oracle. Each entry records source IDs, boundary, oracle, capability reason, freshness, task owner, validation evidence IDs, and initializes `closure_result: pending`. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation or design decision exists; never infer it from WOR-61 `none_relevant`. 4. Carry execution-workspace isolation, hydration, and cleanup policy into task and executor context; mutating siblings on the same execution path isolate via prepare_worktree or serialize even when write scopes are disjoint. 5. Use a common contract group before safe parallel work. Contract-decoupled participants depend on the common contract group and accepted prior handoffs, not sibling in-progress implementation output. Create explicit barrier metadata with barrier ID, readiness evidence, and convergence owner. Cross-branch or joint validation belongs to a post-barrier convergence task. 6. Require a compact `executor-result-v1` handoff. Default `acceptance_review.required: false`. Require task review only when the task sets `acceptance_review.required: true`. Do not infer that flag from soft applicability prose. diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index e6f3809..c7b722a 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -21,11 +21,19 @@ def test_compile_evidence_capability_maps_stable_task_local_invariants() -> None: validation = [{"id": "VAL-001", "invariant_ids": ["INV-001"], "capability_reason": "Observes violation."}] - task = {"evidence_capability": {"result": "mapped", "reason": "Required.", "invariants": [{"id": "INV-001", "source_ids": ["REQ-001"], "invariant": "Observable behavior", "boundary": "unit", "oracle": "VAL-001", "capability_reason": "Unit oracle distinguishes violation.", "freshness": "current_task_batch", "task_id": "task-001", "evidence_ids": ["VAL-001"]}]}} + task = {"evidence_capability": {"result": "mapped", "reason": "Required.", "invariants": [{"id": "INV-001", "source_ids": ["REQ-001"], "invariant": "Observable behavior", "boundary": "unit", "oracle": "VAL-001", "capability_reason": "Unit oracle distinguishes violation.", "freshness": "current_task_batch", "task_id": "task-001", "evidence_ids": ["VAL-001"], "closure_result": "pending"}]}} result = execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], validation) assert result is not None and result["invariants"][0]["id"] == "INV-001" +def test_compile_evidence_capability_requires_closure_result() -> None: + validation = [{"id": "VAL-001", "invariant_ids": ["INV-001"], "capability_reason": "Observes violation."}] + invariant = {"id": "INV-001", "source_ids": ["REQ-001"], "invariant": "Observable behavior", "boundary": "unit", "oracle": "VAL-001", "capability_reason": "Unit oracle distinguishes violation.", "freshness": "current_task_batch", "task_id": "task-001", "evidence_ids": ["VAL-001"]} + task = {"evidence_capability": {"result": "mapped", "reason": "Required.", "invariants": [invariant]}} + with pytest.raises(SystemExit, match="closure_result"): + execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], validation) + + def test_compile_evidence_capability_requires_explicit_result() -> None: with pytest.raises(SystemExit, match="required"): execution_context._compile_evidence_capability({}, "task-001", ["REQ-001"], []) @@ -768,6 +776,23 @@ def test_build_task_brief_rejects_protected_credential_path_scope(tmp_path: Path def test_build_review_package_contains_only_bounded_task_diff_and_evidence(tmp_path: Path) -> None: root, _, task = workspace(tmp_path) + task.write_text( + task.read_text(encoding="utf-8") + .replace( + " result: no_validation_bearing_obligation\n" + " reason: This shared fixture leaves capability semantics to scenario-specific tests.\n" + " invariants: []\n", + " result: mapped\n" + " reason: This scenario verifies review-package propagation.\n" + " invariants:\n" + " - {id: INV-001, source_ids: [REQ-003, TEST-004], invariant: Review package carries allocated capability, boundary: unit, oracle: VAL-001, capability_reason: The focused process distinguishes omission, freshness: current_task_batch, task_id: task-004, evidence_ids: [VAL-001], closure_result: pending}\n", + ) + .replace( + " - {kind: process, command: uv run --with pytest pytest -q tests/test_one.py, proves: TEST-004, expected: exit 0}\n", + " - {id: VAL-001, invariant_ids: [INV-001], capability_reason: The focused process distinguishes omission, kind: process, command: uv run --with pytest pytest -q tests/test_one.py, proves: TEST-004, expected: exit 0}\n", + ), + encoding="utf-8", + ) source = root / WRITE_SCOPE_FILE source.parent.mkdir(parents=True, exist_ok=True) source.write_text("def compile_task():\n return 'old'\n", encoding="utf-8") @@ -829,6 +854,11 @@ def test_build_review_package_contains_only_bounded_task_diff_and_evidence(tmp_p assert "dev-test-driven-development" in package assert "## Review rubric" in package assert "## Accepted Truth Basis" in package + assert "## Evidence capability" in package + assert "INV-001" in package + assert "VAL-001" in package + assert "closure_result" in package + assert "pending" in package assert "## Knowledge disposition" in package assert "No stable authority changed." in package assert "SHOULD-NOT-APPEAR" not in package From d570d489a7674585402a5df17ed9a6996bc7e5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:44:29 +0800 Subject: [PATCH 5/9] feat(orchestration): enforce evidence closure integrity --- .../contract/handoff-executor-result-v1.md | 15 ++- .../assets/orchestration/contract/task-v1.md | 2 +- references/assets/orchestration/workflow.md | 2 +- references/evals/orchestration/evals.json | 6 ++ scripts/orchestration/execution_context.py | 95 ++++++++++++++++++- skills/orch-execute-plan/SKILL.md | 2 +- skills/orch-review-plan/SKILL.md | 1 + tests/test_orchestration_execution_context.py | 82 ++++++++++++++++ .../test_orchestration_workflow_contracts.py | 5 + 9 files changed, 203 insertions(+), 7 deletions(-) diff --git a/references/assets/orchestration/contract/handoff-executor-result-v1.md b/references/assets/orchestration/contract/handoff-executor-result-v1.md index e462a0d..868ee67 100644 --- a/references/assets/orchestration/contract/handoff-executor-result-v1.md +++ b/references/assets/orchestration/contract/handoff-executor-result-v1.md @@ -46,10 +46,22 @@ changes: validation: commands: - - command: "exact command" + - id: VAL-001 + invariant_ids: [INV-001] + command: "exact command" result: passed | failed | skipped note: "Failure reason or skip reason only." +evidence_closure: + result: passed | incapable | contradictory | stale | wrong_boundary | failed | missing | unexecuted + invariants: + - id: INV-001 + boundary: unit | component | integration | runtime | ui_visual | performance | accessibility | inspection | other + freshness: current_task_batch + evidence_ids: [VAL-001] + closure_result: passed | incapable | contradictory | stale | wrong_boundary | failed | missing | unexecuted + repair_owner: null | task | plan | specification + knowledge_disposition: action: none | update | supersede | reclassify reason: "Task-local post-validation evidence." @@ -154,6 +166,7 @@ allocation_evidence: - For a task-scoped executor-result, `related.plan` and `related.task` are required and must equal the assigned task's `plan_id` and `id`. Nested `related.plan` and flat `related_plan` must resolve to exactly one identity. Missing, null, conflicting, or mismatched plan identity fails closed before `Completed` and before `build-review-package` produces a review package. The shared `validate-executor-result` helper owns this gate. Do not infer plan identity from a local task ID. - `changes.files` is required when files, symbols, artifacts, schemas, commands, or docs changed or were inspected as the task output. - `validation.commands` is required when any command, test, lint, inspection, or manual verification was run or intentionally skipped. +- `evidence_closure` is required for a completed task whose compiled `evidence_capability.result` is `mapped`. Its invariant IDs, boundary, freshness, and evidence IDs must exactly match allocated task authority. Each referenced validation report carries the allocated `id` and `invariant_ids`; direct harness observation reuses those compiled identities. Only all-`passed` capable, current, correctly bounded evidence closes the task. Negative results fail closed and name the first repair owner: task for implementation/evidence failure, plan for missing or wrong allocation, specification for absent or contradictory accepted authority. Executor-authored closure is corroboration and cannot replace harness observation or semantic review. - `knowledge_disposition` is required for every completed or partial meaningful move. It records task-local evidence only and does not authorize durable-knowledge retrieval or writes. A change action requires allocated `AUTH-NNN` aliases from the task's accepted decision authority, allocated source IDs, or exact paths already present in the compiled task scope; `none` requires an empty affected-authority list. Invented or unallocated AUTH aliases fail closed. - `contract_decoupling` is required when a task is marked contract-decoupled or depends on a common contract group. - `barrier` is required when a task is a barrier participant or convergence owner. diff --git a/references/assets/orchestration/contract/task-v1.md b/references/assets/orchestration/contract/task-v1.md index d69338f..e6f71b8 100644 --- a/references/assets/orchestration/contract/task-v1.md +++ b/references/assets/orchestration/contract/task-v1.md @@ -70,7 +70,7 @@ evidence_capability: result: mapped | no_validation_bearing_obligation reason: [non-empty reason] invariants: - - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | component | integration | runtime | ui_visual | performance | accessibility | inspection | other, other_mechanism: required-when-other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001], closure_result: pending | passed | incapable | contradictory | stale | wrong_boundary | failed | missing | unexecuted} + - {id: INV-001, source_ids: [REQ-001, AC-001], invariant: string, boundary: unit | component | integration | runtime | ui_visual | performance | accessibility | inspection | other, other_mechanism: required-when-other, oracle: VAL-001, capability_reason: string, freshness: current_task_batch, task_id: task-001, evidence_ids: [VAL-001], closure_result: pending} --- # TASK-001: [Task Name] diff --git a/references/assets/orchestration/workflow.md b/references/assets/orchestration/workflow.md index 7cd5161..f4d69f4 100644 --- a/references/assets/orchestration/workflow.md +++ b/references/assets/orchestration/workflow.md @@ -118,7 +118,7 @@ Specification authoring materializes `impact_decisions` from bounded current-sta Within existing Design Interrogation, specification authoring also records one `excellence_applicability` result after one compact pass: `no_material_opportunity` with an evidence-backed reason, or `material_opportunities` with proposals selected from task evidence and change shape rather than a universal checklist. Surface an option only when accepting or rejecting it could change a requirement, constraint, acceptance criterion, user-observable or contractual outcome, architectural boundary, measurable quality target, validation target, or declared boundary. Each proposal records user value, evidence, cost, risk, recommendation, and `accepted | rejected | deferred | not_material`; unanswered proposals become deferred. Only accepted proposals may project through stable IDs into authoritative requirements, constraints, interfaces, acceptance criteria, or validation targets. Other proposals remain traceable but excluded from planning, executor briefs, and acceptance obligations. The pass stops when further exploration could change none of those surfaces, records the reason, and ensures every surfaced proposal has a disposition. It does not add a lifecycle stage, force a recommendation, or make optional proposals blocking unless accepted projection is incomplete or an unresolved safety or authority conflict exists. The excellence-applicability view is compared by semantic convergence, while agent judgment owns opportunity materiality and recommendation quality. -Planning allocates every accepted validation-bearing obligation or design decision to stable `evidence_capability` entries before execution. Each entry names `source_ids`, invariant, boundary, oracle, `capability_reason`, `freshness`, `task_id`, task-local `evidence_ids`, and initializes `closure_result: pending`; task briefs and review packages compile only the owning task's entries. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation exists, never from a WOR-61 `none_relevant` impact result alone. Select the lightest capable boundary per invariant rather than imposing universal runtime, browser, visual, performance, or E2E proof. Mechanical helpers validate IDs, completeness, task ownership, and provenance; agents own semantic capability judgment. +Planning allocates every accepted validation-bearing obligation or design decision to stable `evidence_capability` entries before execution. Each entry names `source_ids`, invariant, boundary, oracle, `capability_reason`, `freshness`, `task_id`, task-local `evidence_ids`, and initializes `closure_result: pending`; task briefs and review packages compile only the owning task's entries. A completed mapped task returns `evidence_closure` under the same INV/VAL identities. The harness observes those compiled validation items directly and closure fails on missing, incapable, contradictory, stale, wrong-boundary, failed, or unexecuted evidence, routing repair to the first owning task, plan, or specification. Executor-authored closure is corroboration, not independent proof. Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation exists, never from a WOR-61 `none_relevant` impact result alone. Select the lightest capable boundary per invariant rather than imposing universal runtime, browser, visual, performance, or E2E proof. Mechanical helpers validate IDs, completeness, task ownership, provenance, and observed results; agents own semantic capability judgment. ## Lightweight development lane diff --git a/references/evals/orchestration/evals.json b/references/evals/orchestration/evals.json index 5a5625d..c67cc49 100644 --- a/references/evals/orchestration/evals.json +++ b/references/evals/orchestration/evals.json @@ -510,6 +510,12 @@ "prompt": "A plan maps a presentation invariant to a unit oracle that cannot observe rendering, while a lighter capable visual inspection is available.", "expected_output": "Rejects the incapable unit mapping and allocates the lightest capable evidence boundary for the named invariant; it does not impose a universal UI gate on unrelated invariants.", "files": [] + }, + { + "id": 92, + "prompt": "A completed executor result reports a green command but omits allocated INV/VAL identities, or reports incapable, contradictory, stale, wrong-boundary, failed, or unexecuted evidence.", + "expected_output": "Blocks terminal closure, treats executor closure as corroboration rather than harness proof, and routes repair to the first owning task, plan, or specification while preserving optional task review and WOR-59 G9 separation.", + "files": [] } ], "v4_evals": [ diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index f83dd65..d67362a 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -704,8 +704,8 @@ def _compile_evidence_capability( raise SystemExit(f"Evidence capability {invariant_id} missing {field}") if str(item.get("oracle")) not in evidence_ids: raise SystemExit(f"Evidence capability {invariant_id} oracle must name an allocated evidence ID") - if item.get("closure_result") not in EVIDENCE_CLOSURE_RESULTS: - raise SystemExit(f"Evidence capability {invariant_id} has an invalid closure_result") + if item.get("closure_result") != "pending": + raise SystemExit(f"Evidence capability {invariant_id} closure_result must be initialized to pending") for evidence_id in evidence_ids: if invariant_id not in _as_list(validation_by_id[evidence_id].get("invariant_ids")): raise SystemExit(f"Validation {evidence_id} does not bind {invariant_id}") @@ -715,6 +715,84 @@ def _compile_evidence_capability( return {"result": "mapped", "reason": reason, "invariants": compiled} +def _validate_evidence_closure( + handoff: dict[str, Any], + task: dict[str, Any], + state: str, + reported_commands: dict[str, dict[str, Any]], + observed_validation: list[dict[str, Any]] | None, +) -> dict[str, Any]: + capability = task.get("evidence_capability") + if not isinstance(capability, dict): + raise SystemExit("Compiled task is missing evidence_capability authority") + if capability.get("result") == "no_validation_bearing_obligation": + return {"result": "no_validation_bearing_obligation", "invariants": []} + if capability.get("result") != "mapped" or state != "completed": + return {"result": "not-terminal", "invariants": []} + closure = handoff.get("evidence_closure") + if not isinstance(closure, dict): + raise SystemExit("Executor result is missing evidence_closure for mapped invariants") + allocated = { + str(item.get("id")): item + for item in _as_list(capability.get("invariants")) + if isinstance(item, dict) and _nonempty_text(item.get("id")) + } + entries = [item for item in _as_list(closure.get("invariants")) if isinstance(item, dict)] + by_id = {str(item.get("id")): item for item in entries if _nonempty_text(item.get("id"))} + if len(by_id) != len(entries) or set(by_id) != set(allocated): + raise SystemExit("evidence-closure-blocked: mapped invariant closure IDs are missing or unexpected; route plan") + validation_by_id = { + str(item.get("id")): item + for item in _as_list(task.get("validation")) + if isinstance(item, dict) and _nonempty_text(item.get("id")) + } + observed_by_id = { + str(item.get("id")): item + for item in (observed_validation or []) + if isinstance(item, dict) and _nonempty_text(item.get("id")) + } + for invariant_id, expected in allocated.items(): + actual = by_id[invariant_id] + if actual.get("boundary") != expected.get("boundary"): + raise SystemExit(f"evidence-closure-blocked: {invariant_id} is wrong-boundary; route plan") + if actual.get("freshness") != expected.get("freshness"): + raise SystemExit(f"evidence-closure-blocked: {invariant_id} is stale; route task") + evidence_ids = [str(value) for value in _as_list(actual.get("evidence_ids"))] + if evidence_ids != [str(value) for value in _as_list(expected.get("evidence_ids"))]: + raise SystemExit(f"evidence-closure-blocked: {invariant_id} evidence mapping is missing; route plan") + closure_result = str(actual.get("closure_result") or "missing") + if closure_result not in EVIDENCE_CLOSURE_RESULTS: + raise SystemExit(f"evidence-closure-blocked: {invariant_id} has invalid closure_result") + if closure_result != "passed": + repair_owner = str(actual.get("repair_owner") or "task") + if repair_owner not in {"task", "plan", "specification"}: + raise SystemExit(f"evidence-closure-blocked: {invariant_id} has invalid repair owner") + raise SystemExit( + f"evidence-closure-blocked: {invariant_id} is {closure_result}; route {repair_owner}" + ) + for evidence_id in evidence_ids: + validation = validation_by_id.get(evidence_id) + if validation is None: + raise SystemExit(f"evidence-closure-blocked: {invariant_id} evidence {evidence_id} is missing; route plan") + command = str(validation.get("command") or "").strip() + reported = reported_commands.get(command) + if not isinstance(reported, dict): + raise SystemExit(f"evidence-closure-blocked: {invariant_id} evidence {evidence_id} is unexecuted; route task") + if str(reported.get("id") or "") != evidence_id or invariant_id not in _as_list(reported.get("invariant_ids")): + raise SystemExit(f"evidence-closure-blocked: reported evidence identity for {invariant_id} is missing; route task") + if reported.get("result") != "passed": + raise SystemExit(f"evidence-closure-blocked: {invariant_id} evidence {evidence_id} failed; route task") + if observed_validation is not None: + observed = observed_by_id.get(evidence_id) + if not isinstance(observed, dict) or invariant_id not in _as_list(observed.get("invariant_ids")): + raise SystemExit(f"evidence-closure-blocked: harness evidence for {invariant_id} is missing; route task") + if observed.get("result") != "passed": + raise SystemExit(f"evidence-closure-blocked: harness evidence {evidence_id} failed; route task") + if closure.get("result") != "passed": + raise SystemExit("evidence-closure-blocked: aggregate closure result is not passed") + return {"result": "passed", "invariants": entries} + + def evaluate_knowledge_closure_state( *, upstream_disposition: str, @@ -1050,13 +1128,14 @@ def _observe_validation_item(item: dict[str, Any], execution_root: Path, task: d observed = {"command": command, "result": "skipped", "kind": kind} if kind == "inspection": observed["mechanism"] = str(item.get("mechanism") or "").strip() + observed.update({"id": item.get("id"), "invariant_ids": list(_as_list(item.get("invariant_ids")))}) return observed if kind == "inspection": mechanism = str(item.get("mechanism") or "").strip() if not mechanism: raise SystemExit("Inspection validation requires a named harness-owned mechanism") result_value = _run_named_inspection(mechanism, execution_root, task, item) - return {"command": command, "result": result_value, "kind": "inspection", "mechanism": mechanism} + return {"command": command, "result": result_value, "kind": "inspection", "mechanism": mechanism, "id": item.get("id"), "invariant_ids": list(_as_list(item.get("invariant_ids")))} completed = subprocess.run( command, shell=True, @@ -1069,6 +1148,8 @@ def _observe_validation_item(item: dict[str, Any], execution_root: Path, task: d "command": command, "result": "passed" if completed.returncode == 0 else "failed", "kind": "process", + "id": item.get("id"), + "invariant_ids": list(_as_list(item.get("invariant_ids"))), } @@ -1409,6 +1490,7 @@ def validate_executor_result_for_task( if isinstance(item, dict) and item.get("command") ] observed_validation = None + evidence_closure = None reported_commands: dict[str, dict[str, Any]] = {} if state == "completed" and required_items: reported = handoff.get("validation") if isinstance(handoff.get("validation"), dict) else {} @@ -1437,6 +1519,9 @@ def validate_executor_result_for_task( raise SystemExit( f"Executor result validation for {command} must be {allowed_text}; got {result_value}" ) + evidence_closure = _validate_evidence_closure( + handoff, task, state, reported_commands, None + ) evidence_applicability = _task_evidence_applicability(task) repository_entries: list[dict[str, Any]] = [] @@ -1468,11 +1553,15 @@ def validate_executor_result_for_task( repository_id=repository_id, execution_runtime_root=execution_runtime_root, ) + evidence_closure = _validate_evidence_closure( + handoff, task, state, reported_commands, observed_validation + ) return { "knowledge_disposition": knowledge_disposition, "unresolved": unresolved, "result_state": state, "evidence_applicability": evidence_applicability, + "evidence_closure": evidence_closure, **({"observed_validation": observed_validation} if observed_validation is not None else {}), } diff --git a/skills/orch-execute-plan/SKILL.md b/skills/orch-execute-plan/SKILL.md index 2542749..e38565b 100644 --- a/skills/orch-execute-plan/SKILL.md +++ b/skills/orch-execute-plan/SKILL.md @@ -25,7 +25,7 @@ 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. The helper observes required process/inspection items in the bound worktree as one Git-state-neutral batch, then authorizes from post-execution task-caused delta. 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. +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. ```bash python3 scripts/orch.py validate-executor-result --task --handoff diff --git a/skills/orch-review-plan/SKILL.md b/skills/orch-review-plan/SKILL.md index 4e0b05f..db8058f 100644 --- a/skills/orch-review-plan/SKILL.md +++ b/skills/orch-review-plan/SKILL.md @@ -18,6 +18,7 @@ Verify: - specification, plan, phase, and task status coherence; - executor-result handoffs by applicability; - declared completion evidence corresponds to the compiled Truth Basis, source IDs, expected delta, and remaining AUTH constraints; +- every mapped invariant has capable, current, correctly bounded harness-observed evidence under its allocated INV/VAL identities; treat incapable green, contradiction, staleness, wrong-boundary, failure, or non-execution as negative acceptance evidence and route repair to the first owning task, plan, or specification; - missing `acceptance_review.verdict` blocks only a task that explicitly required independent review; do not require universal task-review evidence; - `acceptance_review.verdict: accept` only for those explicitly required reviews; - declared plan-level/integration acceptance observed on the final integrated workspace; do not start another implementation-review agent to produce plan-level acceptance; diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index c7b722a..0893761 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -34,6 +34,14 @@ def test_compile_evidence_capability_requires_closure_result() -> None: execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], validation) +def test_compile_evidence_capability_rejects_preclosed_invariant() -> None: + validation = [{"id": "VAL-001", "invariant_ids": ["INV-001"], "capability_reason": "Observes violation."}] + invariant = {"id": "INV-001", "source_ids": ["REQ-001"], "invariant": "Observable behavior", "boundary": "unit", "oracle": "VAL-001", "capability_reason": "Unit oracle distinguishes violation.", "freshness": "current_task_batch", "task_id": "task-001", "evidence_ids": ["VAL-001"], "closure_result": "passed"} + task = {"evidence_capability": {"result": "mapped", "reason": "Required.", "invariants": [invariant]}} + with pytest.raises(SystemExit, match="initialized to pending"): + execution_context._compile_evidence_capability(task, "task-001", ["REQ-001"], validation) + + def test_compile_evidence_capability_requires_explicit_result() -> None: with pytest.raises(SystemExit, match="required"): execution_context._compile_evidence_capability({}, "task-001", ["REQ-001"], []) @@ -45,6 +53,80 @@ def test_compile_evidence_capability_allows_agent_decided_bookkeeping_empty_map( assert result is not None and result["invariants"] == [] +def evidence_closure_fixture(*, boundary: str = "component", result: str = "passed") -> tuple[dict, dict, dict, list[dict]]: + task = { + "task_id": "task-001", + "validation": [{"id": "VAL-001", "invariant_ids": ["INV-001"], "command": "true"}], + "evidence_capability": { + "result": "mapped", + "reason": "Required.", + "invariants": [{"id": "INV-001", "boundary": "component", "freshness": "current_task_batch", "evidence_ids": ["VAL-001"], "closure_result": "pending"}], + }, + } + handoff = { + "evidence_closure": { + "result": result, + "invariants": [{"id": "INV-001", "boundary": boundary, "freshness": "current_task_batch", "evidence_ids": ["VAL-001"], "closure_result": result, "repair_owner": None}], + } + } + reported = {"true": {"command": "true", "id": "VAL-001", "invariant_ids": ["INV-001"], "result": "passed"}} + observed = [{"command": "true", "id": "VAL-001", "invariant_ids": ["INV-001"], "result": "passed", "kind": "process"}] + return task, handoff, reported, observed + + +def test_evidence_closure_requires_mapped_terminal_record() -> None: + task, _, reported, observed = evidence_closure_fixture() + with pytest.raises(SystemExit, match="missing evidence_closure"): + execution_context._validate_evidence_closure({}, task, "completed", reported, observed) + + +@pytest.mark.parametrize( + ("closure_result", "repair_owner"), + [ + ("incapable", "task"), + ("contradictory", "specification"), + ("stale", "task"), + ("wrong_boundary", "plan"), + ("failed", "task"), + ("missing", "plan"), + ("unexecuted", "task"), + ], +) +def test_evidence_closure_blocks_negative_results_and_routes_owner( + closure_result: str, repair_owner: str +) -> None: + task, handoff, reported, observed = evidence_closure_fixture(result=closure_result) + handoff["evidence_closure"]["invariants"][0]["repair_owner"] = repair_owner + with pytest.raises(SystemExit, match=f"{closure_result}.*{repair_owner}"): + execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) + + +def test_evidence_closure_rejects_wrong_boundary() -> None: + task, handoff, reported, observed = evidence_closure_fixture(boundary="unit") + with pytest.raises(SystemExit, match="wrong-boundary"): + execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) + + +def test_evidence_closure_accepts_capable_component_without_ui_gate() -> None: + task, handoff, reported, observed = evidence_closure_fixture() + result = execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) + assert result["result"] == "passed" + + +def test_evidence_closure_rejects_executor_report_without_allocated_identity() -> None: + task, handoff, reported, observed = evidence_closure_fixture() + reported["true"].pop("id") + with pytest.raises(SystemExit, match="reported evidence identity"): + execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) + + +def test_evidence_closure_rejects_harness_observation_without_allocated_identity() -> None: + task, handoff, reported, observed = evidence_closure_fixture() + observed[0].pop("id") + with pytest.raises(SystemExit, match="harness evidence"): + execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) + + ACCEPTED_AUTHORITY_PATH = ".work-bundle/knowledge/notes/accepted-authority.md" ACCEPTED_AUTHORITY = "AUTH-001" ACCEPTED_CONSTRAINT = "Executors must not retrieve durable knowledge to reconstruct authority." diff --git a/tests/test_orchestration_workflow_contracts.py b/tests/test_orchestration_workflow_contracts.py index 060cabd..a170567 100644 --- a/tests/test_orchestration_workflow_contracts.py +++ b/tests/test_orchestration_workflow_contracts.py @@ -57,6 +57,11 @@ def _task_brief(*, plan_id: str = "plan-001", task_id: str = "task-001") -> dict "files": {"read": [], "write": []}, "truth_basis": {}, "validation": [], + "evidence_capability": { + "result": "no_validation_bearing_obligation", + "reason": "This structural fixture makes no validation-bearing closure claim.", + "invariants": [], + }, "review_required": False, } From 3108663d56c4fc6e6dd99adccf25020b7f9b0c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:50:40 +0800 Subject: [PATCH 6/9] fix(orchestration): require observed closure evidence --- scripts/orchestration/execution_context.py | 32 ++++++++++++++++--- tests/test_orchestration_execution_context.py | 13 ++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index d67362a..21173e7 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -42,6 +42,16 @@ EVIDENCE_CAPABILITY_RESULTS = {"mapped", "no_validation_bearing_obligation"} EVIDENCE_BOUNDARIES = {"unit", "component", "integration", "runtime", "ui_visual", "performance", "accessibility", "inspection", "other"} EVIDENCE_CLOSURE_RESULTS = {"pending", "passed", "incapable", "contradictory", "stale", "wrong_boundary", "failed", "missing", "unexecuted"} +EVIDENCE_REPAIR_OWNERS = { + "pending": "task", + "incapable": "task", + "contradictory": "specification", + "stale": "task", + "wrong_boundary": "plan", + "failed": "task", + "missing": "plan", + "unexecuted": "task", +} KNOWLEDGE_PERSISTENCE_INSTRUCTION_RE = re.compile( r"(?:\.work-bundle/knowledge(?:/|\b)|\bks-[a-z0-9-]+\b)", re.IGNORECASE, @@ -729,6 +739,8 @@ def _validate_evidence_closure( return {"result": "no_validation_bearing_obligation", "invariants": []} if capability.get("result") != "mapped" or state != "completed": return {"result": "not-terminal", "invariants": []} + if observed_validation is None: + raise SystemExit("evidence-closure-blocked: completed mapped invariants require independent harness observation") closure = handoff.get("evidence_closure") if not isinstance(closure, dict): raise SystemExit("Executor result is missing evidence_closure for mapped invariants") @@ -765,8 +777,11 @@ def _validate_evidence_closure( raise SystemExit(f"evidence-closure-blocked: {invariant_id} has invalid closure_result") if closure_result != "passed": repair_owner = str(actual.get("repair_owner") or "task") - if repair_owner not in {"task", "plan", "specification"}: - raise SystemExit(f"evidence-closure-blocked: {invariant_id} has invalid repair owner") + expected_owner = EVIDENCE_REPAIR_OWNERS[closure_result] + if repair_owner != expected_owner: + raise SystemExit( + f"evidence-closure-blocked: {invariant_id} {closure_result} must route {expected_owner}" + ) raise SystemExit( f"evidence-closure-blocked: {invariant_id} is {closure_result}; route {repair_owner}" ) @@ -1519,9 +1534,16 @@ def validate_executor_result_for_task( raise SystemExit( f"Executor result validation for {command} must be {allowed_text}; got {result_value}" ) - evidence_closure = _validate_evidence_closure( - handoff, task, state, reported_commands, None - ) + capability = task.get("evidence_capability") if isinstance(task.get("evidence_capability"), dict) else {} + if state == "completed" and capability.get("result") == "mapped": + if not observe: + raise SystemExit( + "evidence-closure-blocked: completed mapped invariants require independent harness observation" + ) + else: + evidence_closure = _validate_evidence_closure( + handoff, task, state, reported_commands, None + ) evidence_applicability = _task_evidence_applicability(task) repository_entries: list[dict[str, Any]] = [] diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 0893761..aefcedb 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -113,6 +113,19 @@ def test_evidence_closure_accepts_capable_component_without_ui_gate() -> None: assert result["result"] == "passed" +def test_evidence_closure_rejects_passed_executor_claim_without_harness_observation() -> None: + task, handoff, reported, _ = evidence_closure_fixture() + with pytest.raises(SystemExit, match="independent harness observation"): + execution_context._validate_evidence_closure(handoff, task, "completed", reported, None) + + +def test_evidence_closure_rejects_incorrect_first_repair_owner() -> None: + task, handoff, reported, observed = evidence_closure_fixture(result="contradictory") + handoff["evidence_closure"]["invariants"][0]["repair_owner"] = "task" + with pytest.raises(SystemExit, match="must route specification"): + execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) + + def test_evidence_closure_rejects_executor_report_without_allocated_identity() -> None: task, handoff, reported, observed = evidence_closure_fixture() reported["true"].pop("id") From d03c16a48956ab7566ab10383415a4e988cd86d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 16:53:29 +0800 Subject: [PATCH 7/9] fix(orchestration): require produced observation batch --- scripts/orchestration/execution_context.py | 5 ++++ tests/test_orchestration_execution_context.py | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index 21173e7..b838673 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -1578,6 +1578,11 @@ def validate_executor_result_for_task( evidence_closure = _validate_evidence_closure( handoff, task, state, reported_commands, observed_validation ) + if state == "completed" and capability.get("result") == "mapped": + if observed_validation is None or not isinstance(evidence_closure, dict) or evidence_closure.get("result") != "passed": + raise SystemExit( + "evidence-closure-blocked: completed mapped invariants require produced harness observations and passed evidence closure" + ) return { "knowledge_disposition": knowledge_disposition, "unresolved": unresolved, diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index aefcedb..6cfbacb 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -140,6 +140,36 @@ def test_evidence_closure_rejects_harness_observation_without_allocated_identity execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) +def test_completed_mapped_result_requires_produced_observation_batch() -> None: + task, handoff, _, _ = evidence_closure_fixture() + task.update( + { + "plan_id": "plan-001", + "source_ids": [], + "files": {"read": [], "write": []}, + "truth_basis": {}, + "validation": [], + "review_required": False, + "evidence_applicability": { + "metadata": {"required": False, "reasons": []}, + "repository": {"required": False, "reasons": []}, + "codegraph": {"required": False, "reasons": []}, + }, + } + ) + handoff.update( + { + "type": "executor-result", + "related": {"plan": "plan-001", "task": "task-001"}, + "result": {"state": "completed"}, + "task_fit_check": {"task": "task-001", "result": "clean"}, + "knowledge_disposition": {"action": "none", "reason": "No stable authority changed.", "affected_authority": []}, + } + ) + with pytest.raises(SystemExit, match="produced harness observations"): + execution_context.validate_executor_result_for_task(handoff, task, observe=True) + + ACCEPTED_AUTHORITY_PATH = ".work-bundle/knowledge/notes/accepted-authority.md" ACCEPTED_AUTHORITY = "AUTH-001" ACCEPTED_CONSTRAINT = "Executors must not retrieve durable knowledge to reconstruct authority." From 7732c63631fbf92d7ad7c10cc9cfc62dcaf03d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=87=E9=BE=99?= Date: Sun, 30 Aug 2026 17:01:17 +0800 Subject: [PATCH 8/9] fix(orchestration): route incapable evidence to planning --- .../contract/handoff-executor-result-v1.md | 2 +- scripts/orchestration/execution_context.py | 2 +- tests/test_orchestration_execution_context.py | 16 +++++++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/references/assets/orchestration/contract/handoff-executor-result-v1.md b/references/assets/orchestration/contract/handoff-executor-result-v1.md index 868ee67..0d60634 100644 --- a/references/assets/orchestration/contract/handoff-executor-result-v1.md +++ b/references/assets/orchestration/contract/handoff-executor-result-v1.md @@ -166,7 +166,7 @@ allocation_evidence: - For a task-scoped executor-result, `related.plan` and `related.task` are required and must equal the assigned task's `plan_id` and `id`. Nested `related.plan` and flat `related_plan` must resolve to exactly one identity. Missing, null, conflicting, or mismatched plan identity fails closed before `Completed` and before `build-review-package` produces a review package. The shared `validate-executor-result` helper owns this gate. Do not infer plan identity from a local task ID. - `changes.files` is required when files, symbols, artifacts, schemas, commands, or docs changed or were inspected as the task output. - `validation.commands` is required when any command, test, lint, inspection, or manual verification was run or intentionally skipped. -- `evidence_closure` is required for a completed task whose compiled `evidence_capability.result` is `mapped`. Its invariant IDs, boundary, freshness, and evidence IDs must exactly match allocated task authority. Each referenced validation report carries the allocated `id` and `invariant_ids`; direct harness observation reuses those compiled identities. Only all-`passed` capable, current, correctly bounded evidence closes the task. Negative results fail closed and name the first repair owner: task for implementation/evidence failure, plan for missing or wrong allocation, specification for absent or contradictory accepted authority. Executor-authored closure is corroboration and cannot replace harness observation or semantic review. +- `evidence_closure` is required for a completed task whose compiled `evidence_capability.result` is `mapped`. Its invariant IDs, boundary, freshness, and evidence IDs must exactly match allocated task authority. Each referenced validation report carries the allocated `id` and `invariant_ids`; direct harness observation reuses those compiled identities. Only all-`passed` capable, current, correctly bounded evidence closes the task. Negative results fail closed and name the first repair owner: task for failed, stale, or unexecuted implementation evidence; plan for missing, wrong-boundary, or incapable allocation; specification for contradictory accepted authority. Executor-authored closure is corroboration and cannot replace harness observation or semantic review. - `knowledge_disposition` is required for every completed or partial meaningful move. It records task-local evidence only and does not authorize durable-knowledge retrieval or writes. A change action requires allocated `AUTH-NNN` aliases from the task's accepted decision authority, allocated source IDs, or exact paths already present in the compiled task scope; `none` requires an empty affected-authority list. Invented or unallocated AUTH aliases fail closed. - `contract_decoupling` is required when a task is marked contract-decoupled or depends on a common contract group. - `barrier` is required when a task is a barrier participant or convergence owner. diff --git a/scripts/orchestration/execution_context.py b/scripts/orchestration/execution_context.py index b838673..b8005fe 100644 --- a/scripts/orchestration/execution_context.py +++ b/scripts/orchestration/execution_context.py @@ -44,7 +44,7 @@ EVIDENCE_CLOSURE_RESULTS = {"pending", "passed", "incapable", "contradictory", "stale", "wrong_boundary", "failed", "missing", "unexecuted"} EVIDENCE_REPAIR_OWNERS = { "pending": "task", - "incapable": "task", + "incapable": "plan", "contradictory": "specification", "stale": "task", "wrong_boundary": "plan", diff --git a/tests/test_orchestration_execution_context.py b/tests/test_orchestration_execution_context.py index 6cfbacb..55d82c1 100644 --- a/tests/test_orchestration_execution_context.py +++ b/tests/test_orchestration_execution_context.py @@ -83,7 +83,7 @@ def test_evidence_closure_requires_mapped_terminal_record() -> None: @pytest.mark.parametrize( ("closure_result", "repair_owner"), [ - ("incapable", "task"), + ("incapable", "plan"), ("contradictory", "specification"), ("stale", "task"), ("wrong_boundary", "plan"), @@ -119,10 +119,16 @@ def test_evidence_closure_rejects_passed_executor_claim_without_harness_observat execution_context._validate_evidence_closure(handoff, task, "completed", reported, None) -def test_evidence_closure_rejects_incorrect_first_repair_owner() -> None: - task, handoff, reported, observed = evidence_closure_fixture(result="contradictory") - handoff["evidence_closure"]["invariants"][0]["repair_owner"] = "task" - with pytest.raises(SystemExit, match="must route specification"): +@pytest.mark.parametrize( + ("closure_result", "wrong_owner", "expected_owner"), + [("incapable", "task", "plan"), ("contradictory", "task", "specification")], +) +def test_evidence_closure_rejects_incorrect_first_repair_owner( + closure_result: str, wrong_owner: str, expected_owner: str +) -> None: + task, handoff, reported, observed = evidence_closure_fixture(result=closure_result) + handoff["evidence_closure"]["invariants"][0]["repair_owner"] = wrong_owner + with pytest.raises(SystemExit, match=f"must route {expected_owner}"): execution_context._validate_evidence_closure(handoff, task, "completed", reported, observed) From 4697f4b9dd07d7e8d84617162e0a30399c0aabf9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 30 Aug 2026 09:07:41 +0000 Subject: [PATCH 9/9] fix(orchestration): enforce evidence capability at review completion Require orch-review-completion and orch-review-plan to block closure unless each mapped invariant has capable current harness-observed evidence, while keeping WOR-59 G9 as the separate post-execution classifier. Co-authored-by: Ldsystem --- rules/orchestration/orch-review-completion.md | 10 ++++++ skills/orch-review-plan/SKILL.md | 18 ++++++++++- .../test_orchestration_skill_rule_boundary.py | 31 +++++++++++++++++++ .../test_orchestration_workflow_contracts.py | 6 ++++ tests/test_rule_contracts.py | 6 ++++ 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/rules/orchestration/orch-review-completion.md b/rules/orchestration/orch-review-completion.md index 7208d16..4034ef4 100644 --- a/rules/orchestration/orch-review-completion.md +++ b/rules/orchestration/orch-review-completion.md @@ -18,6 +18,10 @@ Keep final review focused on whether the WorkBundle workflow completed correctly - Confirm each required task review compared the accepted Truth Basis, implementation, test oracle, and task-local knowledge disposition before accepting the task. - Check that declared completion evidence corresponds to the compiled Truth Basis, source IDs, expected delta, and remaining AUTH constraints. +- Before archive or completion, confirm every accepted validation-bearing invariant has a compiled `evidence_capability` entry and capable, current, correctly bounded harness-observed evidence under its allocated INV/VAL identities. Treat incapable green, contradiction, staleness, wrong-boundary, failure, missing, or unexecuted evidence as negative acceptance evidence, not closure. +- Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation or design decision exists. Do not infer an empty evidence-capability map from a WOR-61 `none_relevant` impact result. +- Route first-owner repair for this pre-closure oracle-capability check: task repair for failed, stale, or unexecuted implementation evidence; plan repair for missing, wrong-boundary, or incapable allocation; specification repair for contradictory accepted authority. +- Keep this pre-closure oracle-capability check distinct from `RuntimeVerificationClassificationV1`. WOR-59 G9 remains the unchanged post-execution classifier and may use this map only as evidence when triggered. Mechanical helpers validate IDs, completeness, provenance, and observed results; agents own semantic capability judgment and must not impose a universal browser, E2E, production, or runtime gate. - Missing `acceptance_review.verdict` blocks only a task that explicitly required independent review. Do not require universal task-review evidence. - Keep approved `ks-*` persistence delegation review-owned; executor disposition evidence never authorizes knowledge retrieval or writes. @@ -49,6 +53,10 @@ Keep final review focused on whether the WorkBundle workflow completed correctly - Do not substitute project-file inspection for accepted task-review evidence on tasks that explicitly required review. - Do not create a repair specification for every failed review gate. - Do not archive while required knowledge, validation, review, repository, or workspace evidence is unresolved. +- Do not close an invariant on a green oracle that cannot observe it or that contradicts accepted authority. +- Do not treat WOR-59 G9 classification as this pre-closure oracle-capability check, or replace G9 with it. +- Do not infer an empty evidence-capability map from a WOR-61 `none_relevant` impact result. +- Do not impose a universal browser, E2E, production, or runtime gate. - Do not directly write durable knowledge from orchestration. ## Validation @@ -56,6 +64,8 @@ Keep final review focused on whether the WorkBundle workflow completed correctly - Confirm every completed review-required task has fresh validation, a valid executor-result handoff, and `accept` review evidence. - Confirm missing `acceptance_review.verdict` is not a blocker unless the task explicitly required review. - Confirm declared completion evidence matches the compiled Truth Basis, source IDs, and AUTH constraints. +- Confirm every mapped invariant has capable, current, correctly bounded harness-observed evidence under its allocated INV/VAL identities, or a typed first-owner repair route; confirm `no_validation_bearing_obligation` is not inferred from WOR-61 `none_relevant`. +- Confirm this pre-closure oracle-capability check remains distinct from `RuntimeVerificationClassificationV1` and that WOR-59 G9 remains the unchanged post-execution classifier. - Confirm blocker routing names the owning resume path instead of restarting the lifecycle. - Confirm finalization and archive occur only after knowledge disposition and deterministic gates resolve. diff --git a/skills/orch-review-plan/SKILL.md b/skills/orch-review-plan/SKILL.md index db8058f..c6d18ca 100644 --- a/skills/orch-review-plan/SKILL.md +++ b/skills/orch-review-plan/SKILL.md @@ -18,7 +18,7 @@ Verify: - specification, plan, phase, and task status coherence; - executor-result handoffs by applicability; - declared completion evidence corresponds to the compiled Truth Basis, source IDs, expected delta, and remaining AUTH constraints; -- every mapped invariant has capable, current, correctly bounded harness-observed evidence under its allocated INV/VAL identities; treat incapable green, contradiction, staleness, wrong-boundary, failure, or non-execution as negative acceptance evidence and route repair to the first owning task, plan, or specification; +- every mapped invariant has capable, current, correctly bounded harness-observed evidence under its allocated INV/VAL identities; treat incapable green, contradiction, staleness, wrong-boundary, failure, missing, or unexecuted evidence as negative acceptance evidence and route first-owner repair: task repair for failed, stale, or unexecuted implementation evidence; plan repair for missing, wrong-boundary, or incapable allocation; specification repair for contradictory accepted authority; - missing `acceptance_review.verdict` blocks only a task that explicitly required independent review; do not require universal task-review evidence; - `acceptance_review.verdict: accept` only for those explicitly required reviews; - declared plan-level/integration acceptance observed on the final integrated workspace; do not start another implementation-review agent to produce plan-level acceptance; @@ -30,6 +30,16 @@ Verify: - approved `ks-*` return evidence exists when durable knowledge was required; - allowed commit, applicable CodeGraph sync, metadata update, archive, and index refresh completed or are explicitly not applicable. +## Evidence capability correspondence + +Before archive or completion, every accepted validation-bearing invariant must have a compiled `evidence_capability` entry and capable, current, correctly bounded harness-observed evidence under its allocated INV/VAL identities. Incapable green, contradiction, staleness, wrong-boundary, failure, missing, or unexecuted evidence is negative acceptance evidence, not closure. + +Use `no_validation_bearing_obligation + reason` only when no accepted validation-bearing obligation or design decision exists. Do not infer an empty evidence-capability map from a WOR-61 `none_relevant` impact result. + +Route first-owner repair for this pre-closure oracle-capability check: task repair for failed, stale, or unexecuted implementation evidence; plan repair for missing, wrong-boundary, or incapable allocation; specification repair for contradictory accepted authority. Mechanical helpers validate IDs, completeness, provenance, and observed results; agents own semantic capability judgment. This is not a universal browser, E2E, production, or runtime gate. + +Keep this pre-closure oracle-capability check distinct from `RuntimeVerificationClassificationV1`. WOR-59 G9 remains the unchanged post-execution classifier and may use this map only as evidence when triggered. + ## Runtime verification classification When a runtime or UI defect is reported after execution, or an accepted specification or plan explicitly claims runtime acceptance of a user-visible invariant, record a `RuntimeVerificationClassificationV1` before archive or residual feature routing. Review the authority chain in order: original user request and accepted specification; compiled plan and task acceptance criteria; executor handoffs and produced commits; then execution-introduced behavior. @@ -57,6 +67,12 @@ workspace preparation/cleanup/finalization incomplete -> workspace-blocked -> bounded execution-workspace helper implementation rejected -> task repair and independent re-review +failed, stale, or unexecuted implementation evidence + -> task repair +incapable, missing, or wrong-boundary allocation + -> plan repair +contradictory accepted authority + -> specification repair plan decomposition defect -> repair plan only requirement/design/authority defect diff --git a/tests/test_orchestration_skill_rule_boundary.py b/tests/test_orchestration_skill_rule_boundary.py index 9d93c72..fd76eba 100644 --- a/tests/test_orchestration_skill_rule_boundary.py +++ b/tests/test_orchestration_skill_rule_boundary.py @@ -171,10 +171,41 @@ def test_final_review_is_workflow_audit_not_code_review() -> None: "implementation_gap", "new_feature", "uncovered_fixture", + "evidence_capability", + "INV/VAL", + "incapable green", + "pre-closure oracle-capability check", + "no_validation_bearing_obligation", + "none_relevant", ]: assert token in text +def test_review_enforces_evidence_capability_before_closure() -> None: + for relative in [ + "skills/orch-review-plan/SKILL.md", + "rules/orchestration/orch-review-completion.md", + ]: + text = read(relative) + for token in [ + "evidence_capability", + "INV/VAL", + "incapable green", + "wrong-boundary", + "harness-observed", + "no_validation_bearing_obligation", + "none_relevant", + "pre-closure oracle-capability check", + "task repair", + "plan repair", + "specification repair", + "RuntimeVerificationClassificationV1", + "WOR-59 G9 remains the unchanged post-execution classifier", + "universal browser, E2E, production, or runtime gate", + ]: + assert token in text, f"{relative}: {token}" + + def test_runtime_verification_classification_contract_routes_the_first_broken_artifact() -> None: for relative in [ "skills/orch-review-plan/SKILL.md", diff --git a/tests/test_orchestration_workflow_contracts.py b/tests/test_orchestration_workflow_contracts.py index a170567..31f5295 100644 --- a/tests/test_orchestration_workflow_contracts.py +++ b/tests/test_orchestration_workflow_contracts.py @@ -878,6 +878,12 @@ def test_review_rule_uses_typed_resume_routing() -> None: "accepted `update`, `supersede`, or `reclassify`", "rejected dispositions", "archive", + "evidence_capability", + "INV/VAL", + "incapable green", + "pre-closure oracle-capability check", + "no_validation_bearing_obligation", + "WOR-59 G9 remains the unchanged post-execution classifier", ]: assert token in rule diff --git a/tests/test_rule_contracts.py b/tests/test_rule_contracts.py index 663304d..d71b93a 100644 --- a/tests/test_rule_contracts.py +++ b/tests/test_rule_contracts.py @@ -552,6 +552,12 @@ def test_orchestration_rules_require_contract_barrier_and_review_settlement_evid assert "Create or require plan repair only for a decomposition defect" in review assert "specification repair only for a requirement, design, or authority defect" in review assert "Do not create a repair specification for every failed review gate" in review + assert "evidence_capability" in review + assert "INV/VAL" in review + assert "incapable green" in review + assert "pre-closure oracle-capability check" in review + assert "no_validation_bearing_obligation" in review + assert "WOR-59 G9 remains the unchanged post-execution classifier" in review def test_execution_and_review_skills_carry_task003_flow_requirements() -> None: