diff --git a/data/contribution-workspaces/fork-bootstrap-proposal.json b/data/contribution-workspaces/fork-bootstrap-proposal.json index 16ff2a4a..6e6f2bba 100644 --- a/data/contribution-workspaces/fork-bootstrap-proposal.json +++ b/data/contribution-workspaces/fork-bootstrap-proposal.json @@ -3,7 +3,7 @@ "state": "READ_ONLY_PROPOSAL_NOT_APPROVED", "destination_org": "opensiro-contributions", "source_registry": "data/contribution-workspaces/registry.json", - "source_registry_sha256": "ff91c1b0b961f134021291b89cdd0e6a52b4d57dcb26f4238edd9fc0a68dfca1", + "source_registry_sha256": "786365a1d9c8c9f611bb7c6c188393fac9ad91b9d3bcf952839e4b917e9e5708", "priority_snapshot": "data/fork-priorities/2026-11-08.json", "batch_size_max": 10, "counts": { @@ -35,7 +35,7 @@ { "id": "bootstrap-001", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "c9f7a324b1bf54053fff2a85963862b3b271e80ae6a7dee27b56b422d5e62069", + "selection_sha256": "1ff7ff9df10dfba2d8664a7c7cfe5ee5f7e2a33d6579b5f7f4f1b9553b732a2b", "source_count": 10, "has_collision_name_proposals": true, "selection": [ @@ -84,7 +84,7 @@ { "id": "bootstrap-002", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "2e84240f54b1c9c949ba9f542c2e0a2b70a6856c3139f9343d67f05b2c030895", + "selection_sha256": "d120d63b36c045c2e5136936e22d2c634fd478ffdada5ed42d25e452d94c5fa7", "source_count": 10, "has_collision_name_proposals": true, "selection": [ @@ -133,7 +133,7 @@ { "id": "bootstrap-003", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "a04cac89bc8c64c17a4ea003675c5052f187c71cdb5643f576013791c5532914", + "selection_sha256": "0daa4109db827bdd0aae2ab36de11858a980e5fc74aed4fe8d13783ab4eeebc6", "source_count": 10, "has_collision_name_proposals": false, "selection": [ @@ -182,7 +182,7 @@ { "id": "bootstrap-004", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "adbd52fa31afa99bca06a97b0750d98ceaf0d7f3ea49d91cf767dbb109297e79", + "selection_sha256": "be2872a7c6931128c32590871ab30a00df3ca666e84a4f5f4b1a3ed66261a930", "source_count": 10, "has_collision_name_proposals": false, "selection": [ @@ -231,7 +231,7 @@ { "id": "bootstrap-005", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "90186c90f9cc1dc8f60b0428602467eca49ecd043393272ea18f28023fa4b2a4", + "selection_sha256": "9cd1e015ff69b171b640e7a2a41caa3efbe2802c89d87e4d51104b8268a28597", "source_count": 10, "has_collision_name_proposals": false, "selection": [ @@ -280,7 +280,7 @@ { "id": "bootstrap-006", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "bdd6ae6343113d2112f4bea753a773c5a3d2b01020bf07cdcce2c9f375b655d9", + "selection_sha256": "daf9573ca5d4d8c43029a68f781d3e52c1e8fb7db5ff3441fc52a5479e0e5d07", "source_count": 10, "has_collision_name_proposals": false, "selection": [ @@ -329,7 +329,7 @@ { "id": "bootstrap-007", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "af52919731a2dded222a46a731c9ac74accf138da405f2ff826dd5501edc5ed9", + "selection_sha256": "b1b38584d19cd520e968ff7937607dc181e42cb4834cf642e57bc8660aa175b3", "source_count": 10, "has_collision_name_proposals": true, "selection": [ @@ -378,7 +378,7 @@ { "id": "bootstrap-008", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "64f26d69de687ea36621b891fa54cda59bdb6bb56347b9ce53f6267ad15c9f91", + "selection_sha256": "46433d517ac4b085b724c7bc41d76632f3fd9a0817be12a03c43b1b099756130", "source_count": 10, "has_collision_name_proposals": false, "selection": [ @@ -427,7 +427,7 @@ { "id": "bootstrap-009", "state": "UNAPPROVED_REQUIRES_TRUSTED_PREFLIGHT", - "selection_sha256": "27f7c59ce3225681c1bf4603101d166296fab6afbe47f6975376269d81ea1576", + "selection_sha256": "3a82a3e157d94befe2b47f1464dd770c268b710670c9306bfe0f6202d6b37f2c", "source_count": 1, "has_collision_name_proposals": false, "selection": [ diff --git a/data/contribution-workspaces/registry.json b/data/contribution-workspaces/registry.json index f74d4f8f..591eeae3 100644 --- a/data/contribution-workspaces/registry.json +++ b/data/contribution-workspaces/registry.json @@ -17,10 +17,10 @@ "dated_fork_readbacks": 17, "same_named_ref_matches_at_readback": 10, "technical_packet_states": { - "EXPLICIT_PATCH_HASH_MATCHED": 16, + "EXPLICIT_PATCH_HASH_MATCHED": 18, "LOCAL_MULTI_PATCH_SELECTION_REQUIRED": 1, "LOCAL_SINGLE_PATCH_UNREVIEWED": 33, - "NO_LOCAL_PACKET_OR_PR_ONLY_HANDOFF": 4 + "NO_LOCAL_PACKET_OR_PR_ONLY_HANDOFF": 2 } }, "execution": { @@ -2377,13 +2377,16 @@ "stage": "AWAITING_EXTERNAL", "origin": "ASSESSMENT_DERIVED", "handoff_url": "https://github.com/opensiro/upstream/pull/120", - "patch_path": null, - "patch_sha256": null, + "patch_path": "pilots/evidence-bench/retrieval-error-admission.patch", + "patch_sha256": "0974cdefa228cfdf37e1b750144e01c269c8f2dee6d9aa8ea01e86dd79b23646", "test_patch_path": null, "technical_packet": { - "state": "NO_LOCAL_PACKET_OR_PR_ONLY_HANDOFF", + "state": "EXPLICIT_PATCH_HASH_MATCHED", "pilot": null, - "explicit_patch": null, + "explicit_patch": { + "path": "pilots/evidence-bench/retrieval-error-admission.patch", + "sha256": "0974cdefa228cfdf37e1b750144e01c269c8f2dee6d9aa8ea01e86dd79b23646" + }, "explicit_test_patch": null } } @@ -2870,13 +2873,16 @@ "stage": "AWAITING_EXTERNAL", "origin": "ASSESSMENT_DERIVED", "handoff_url": "https://github.com/opensiro/upstream/pull/121", - "patch_path": null, - "patch_sha256": null, + "patch_path": "pilots/fara/resume-task-identity.patch", + "patch_sha256": "fce42386bd6b1cf1ea0fed6c22a206589239cdeeb37f53cfaa48eb027da9002e", "test_patch_path": null, "technical_packet": { - "state": "NO_LOCAL_PACKET_OR_PR_ONLY_HANDOFF", + "state": "EXPLICIT_PATCH_HASH_MATCHED", "pilot": null, - "explicit_patch": null, + "explicit_patch": { + "path": "pilots/fara/resume-task-identity.patch", + "sha256": "fce42386bd6b1cf1ea0fed6c22a206589239cdeeb37f53cfaa48eb027da9002e" + }, "explicit_test_patch": null } } diff --git a/data/contribution-workspaces/selected-packets.json b/data/contribution-workspaces/selected-packets.json new file mode 100644 index 00000000..fd935b94 --- /dev/null +++ b/data/contribution-workspaces/selected-packets.json @@ -0,0 +1,21 @@ +{ + "schema_version": 1, + "selections": [ + { + "source": "kstawiski/evidence-gated-scientific-agent", + "candidate_id": "evidence-bench-native-evidence-admission", + "source_sha": "85b396a824dfad6ec0880cbe07d86d3e24920ae8", + "patch_path": "pilots/evidence-bench/retrieval-error-admission.patch", + "patch_sha256": "0974cdefa228cfdf37e1b750144e01c269c8f2dee6d9aa8ea01e86dd79b23646", + "receipt": "reports/2026-11-12-native-packet-requalification-wave-01.md" + }, + { + "source": "microsoft/fara", + "candidate_id": "fara-native-checkpoint-integrity", + "source_sha": "a675d6d61c41c47ae87bacefeab22caad18e3e84", + "patch_path": "pilots/fara/resume-task-identity.patch", + "patch_sha256": "fce42386bd6b1cf1ea0fed6c22a206589239cdeeb37f53cfaa48eb027da9002e", + "receipt": "reports/2026-11-12-native-packet-requalification-wave-01.md" + } + ] +} diff --git a/docs/CONTINUOUS_WORKING_FORK_PIPELINE.md b/docs/CONTINUOUS_WORKING_FORK_PIPELINE.md index b28cce7b..15f4ac5c 100644 --- a/docs/CONTINUOUS_WORKING_FORK_PIPELINE.md +++ b/docs/CONTINUOUS_WORKING_FORK_PIPELINE.md @@ -54,6 +54,18 @@ dated mapping remains `DATED_MAPPING_REQUIRES_LIVE_READBACK`, requires a fresh provider preflight before writes, and does not attest that a branch contains a tested contribution. +## Additive selection of historical native packets + +For a historically frozen screening handoff with an exact native fix held only +in a prior source-only draft, revalidate the original and record the selected +source identity, assessed SHA, patch path/hash, and dated RED/GREEN receipt in +data/contribution-workspaces/selected-packets.json. Never rewrite a frozen +screening manifest. Regenerate the source/workspace registry and check integrity. + +This promotes only the technical-packet state to EXPLICIT_PATCH_HASH_MATCHED. +It does not grant GitHub write permission, prove full source-native tests, +waive a first-party legal gate, or demonstrate maintainer adoption. + ## Single-cycle state and authority 1. **Intake:** reserve one source identity (`owner/repo`); resolve it against diff --git a/pilots/CATALOG.md b/pilots/CATALOG.md index 25cca3af..d28f32b5 100644 --- a/pilots/CATALOG.md +++ b/pilots/CATALOG.md @@ -7,7 +7,7 @@ fitness, fork existence, PR publication, maintainer acceptance, or VSM grades. See [experiment contract](EXPERIMENT_CONTRACT.md) and [working-fork pipeline](../docs/CONTINUOUS_WORKING_FORK_PIPELINE.md). -**Coverage:** 73 indexed pilot directories · 242 files. The frozen baseline protects 66 historical directories and their files; 7 directories were added later. +**Coverage:** 75 indexed pilot directories · 244 files. The frozen baseline protects 66 historical directories and their files; 9 directories were added later. Each row links its preferred entry file. Read the actual receipt and governing batch/PR before attributing positive results or current status. @@ -78,6 +78,13 @@ batch/PR before attributing positive results or current status. | `workflow-one` | [Open](workflow-one/CONTRIBUTION_HANDOFF.md) | patch: 1, handoff: 1 | | `yoagent-file-alias-s2` | [Open](yoagent-file-alias-s2/CONTRIBUTION_HANDOFF.md) | patch: 3, handoff: 1, receipt: 2, other: 1 | +## Patches with other technical evidence (2) + +| Pilot | Entry point | Artifacts | +| --- | --- | --- | +| `evidence-bench` | [Open](evidence-bench/retrieval-error-admission.patch) | patch: 1 | +| `fara` | [Open](fara/resume-task-identity.patch) | patch: 1 | + ## Screened or blocked hypotheses without a patch (16) | Pilot | Entry point | Artifacts | diff --git a/pilots/catalog.json b/pilots/catalog.json index 52e5bd89..d2e687ac 100644 --- a/pilots/catalog.json +++ b/pilots/catalog.json @@ -4,14 +4,14 @@ "meaning": "FILE_INVENTORY_ONLY_NO_SUCCESS_OR_EXTERNAL_PUBLICATION_CLAIM", "generated_from": "pilots/ direct subdirectories and their preserved files", "counts": { - "pilot_directories": 73, - "artifact_files": 242, + "pilot_directories": 75, + "artifact_files": 244, "historical_directories": 66, - "new_directories": 7, + "new_directories": 9, "by_kind": { "EXPERIMENT_SERIES": 4, "PATCH_HANDOFF": 52, - "PATCH_EVIDENCE": 0, + "PATCH_EVIDENCE": 2, "SCREEN_ONLY": 16, "OTHER_EVIDENCE": 1 } @@ -343,6 +343,18 @@ "candidate-episode-snapshot.patch" ] }, + { + "id": "evidence-bench", + "path": "pilots/evidence-bench", + "kind": "PATCH_EVIDENCE", + "start_here": "pilots/evidence-bench/retrieval-error-admission.patch", + "artifact_counts": { + "patch": 1 + }, + "files": [ + "retrieval-error-admission.patch" + ] + }, { "id": "evidenceloom", "path": "pilots/evidenceloom", @@ -357,6 +369,18 @@ "unicode-spaced-numeric-scale.patch" ] }, + { + "id": "fara", + "path": "pilots/fara", + "kind": "PATCH_EVIDENCE", + "start_here": "pilots/fara/resume-task-identity.patch", + "artifact_counts": { + "patch": 1 + }, + "files": [ + "resume-task-identity.patch" + ] + }, { "id": "forgedock", "path": "pilots/forgedock", diff --git a/pilots/evidence-bench/retrieval-error-admission.patch b/pilots/evidence-bench/retrieval-error-admission.patch new file mode 100644 index 00000000..87e93091 --- /dev/null +++ b/pilots/evidence-bench/retrieval-error-admission.patch @@ -0,0 +1,82 @@ +diff --git a/scientific_agent/policy.py b/scientific_agent/policy.py +index a6a57d0..d21cdbf 100644 +--- a/scientific_agent/policy.py ++++ b/scientific_agent/policy.py +@@ -373,13 +373,13 @@ class ToolPolicy: + self.chrome_ready = True + encoded = json.dumps(result, sort_keys=True, default=str).encode("utf-8") + reported_error = result.get("error") if isinstance(result, dict) else None +- if ( +- not reported_error +- and isinstance(result, dict) +- and result.get("status") +- in {"failed", "timed_out", "cancelled", "policy_denied"} +- ): +- reported_error = str(result["status"]).upper() ++ if not reported_error and isinstance(result, dict): ++ status = result.get("status") ++ if isinstance(status, str) and status.strip().lower() in { ++ "error", "failed", "failure", "timed_out", "timeout", ++ "cancelled", "canceled", "policy_denied", ++ }: ++ reported_error = status.strip().upper() + if not reported_error and len(encoded) > MAX_RESULT_ARTIFACT_BYTES: + reported_error = "RESULT_TOO_LARGE" + artifact_error: str | None = None +diff --git a/tests/test_policy.py b/tests/test_policy.py +index f4178b4..c86b023 100644 +--- a/tests/test_policy.py ++++ b/tests/test_policy.py +@@ -229,3 +229,52 @@ def test_cumulative_model_observation_budget_compacts_later_results( + assert second["result_compacted"] is True + assert "result_preview" not in second + assert Path(second["full_result_artifact"]).is_file() ++ ++def test_error_status_does_not_create_retrieval_evidence(tmp_path): ++ gate = policy(tmp_path, preserve_evidence=True) ++ ++ class Tool: ++ name = "query-docs" ++ ++ # Error status without a separate "error" property previously counted as ++ # a successful retrieval and donated the URL in the failure message. ++ result = gate.after_tool( ++ tool=Tool(), ++ args={"query": "citation"}, ++ tool_context=None, ++ tool_response={ ++ "status": "error", ++ "message": "Fetch failed: https://1.1.1.1/research-paper", ++ }, ++ ) ++ assert result is None ++ evidence = gate.retrieval_evidence() ++ assert evidence.successful_calls == 0 ++ assert evidence.urls == [] ++ assert evidence.tools == [] ++ assert evidence.artifacts == [] ++ ledger_events = [ ++ json.loads(line) ++ for line in (tmp_path / "events.jsonl").read_text().splitlines() ++ ] ++ assert ledger_events[-1]["reported_error"] == "ERROR" ++ ++ ++def test_success_status_still_records_retrieval(tmp_path): ++ gate = policy(tmp_path, preserve_evidence=True) ++ ++ class Tool: ++ name = "query-docs" ++ ++ gate.after_tool( ++ tool=Tool(), ++ args={"query": "valid"}, ++ tool_context=None, ++ tool_response={ ++ "status": "success", ++ "content": "Supported: https://1.1.1.1/research-paper", ++ }, ++ ) ++ evidence = gate.retrieval_evidence() ++ assert evidence.successful_calls == 1 ++ assert evidence.urls == ["https://1.1.1.1/research-paper"] diff --git a/pilots/fara/resume-task-identity.patch b/pilots/fara/resume-task-identity.patch new file mode 100644 index 00000000..ad9195c0 --- /dev/null +++ b/pilots/fara/resume-task-identity.patch @@ -0,0 +1,69 @@ +diff --git a/src/fara/core/run_context.py b/src/fara/core/run_context.py +index bddd472..9ea1d27 100644 +--- a/src/fara/core/run_context.py ++++ b/src/fara/core/run_context.py +@@ -53,6 +53,12 @@ class RunContext: + output_path = Path(output_dir) + if output_path.exists() and (output_path / "task.json").exists(): + writer = DataPointWriter.resume(output_path) ++ if writer.data_point.task.task_id != task.task_id: ++ raise ValueError( ++ "Cannot resume task: checkpoint task_id " ++ f"{writer.data_point.task.task_id!r} does not match requested " ++ f"task_id {task.task_id!r}" ++ ) + else: + writer = DataPointWriter.create(output_path, task, run_id=run_id) + ctx = cls( +diff --git a/tests/test_run_context_resume_identity.py b/tests/test_run_context_resume_identity.py +new file mode 100644 +index 0000000..225ee6d +--- /dev/null ++++ b/tests/test_run_context_resume_identity.py +@@ -0,0 +1,46 @@ ++"""Native, no-browser tests for resume task identity invariants.""" ++import sys ++from pathlib import Path ++from types import ModuleType ++ ++import pytest ++ ++# Load first-party core modules without Fara's package-level agent/OpenAI imports. ++source = Path(__file__).resolve().parents[1] / "src" / "fara" ++fara_package = ModuleType("fara") ++fara_package.__path__ = [str(source)] ++core_package = ModuleType("fara.core") ++core_package.__path__ = [str(source / "core")] ++sys.modules.setdefault("fara", fara_package) ++sys.modules.setdefault("fara.core", core_package) ++from fara.core.data_point import Task ++from fara.core.run_context import RunContext ++ ++ ++def test_resume_rejects_different_task_id(tmp_path): ++ original = Task(task_id="experiment-1", instruction="collect A") ++ different = Task(task_id="experiment-2", instruction="collect B") ++ RunContext.create(environment=object(), task=original, output_dir=tmp_path) ++ ++ with pytest.raises(ValueError, match="task"): ++ RunContext.create(environment=object(), task=different, output_dir=tmp_path) ++ ++ # Denial must not overwrite the recorded identity. ++ assert Task.load(tmp_path / "task.json").task_id == original.task_id ++ ++ ++def test_resume_same_task_id_keeps_original_checkpoint(tmp_path): ++ task = Task(task_id="experiment-1", instruction="collect A") ++ first = RunContext.create(environment=object(), task=task, output_dir=tmp_path) ++ first.set_state("stage", "complete") ++ first.checkpoint() ++ ++ resumed = RunContext.create(environment=object(), task=task, output_dir=tmp_path) ++ assert resumed.task.task_id == task.task_id ++ assert resumed.get_state("stage") == "complete" ++ ++ ++def test_new_directory_keeps_requested_task_identity(tmp_path): ++ task = Task(task_id="experiment-2", instruction="new task") ++ created = RunContext.create(environment=object(), task=task, output_dir=tmp_path) ++ assert created.task.task_id == "experiment-2" diff --git a/reports/2026-11-12-native-packet-requalification-wave-01.md b/reports/2026-11-12-native-packet-requalification-wave-01.md new file mode 100644 index 00000000..d704d394 --- /dev/null +++ b/reports/2026-11-12-native-packet-requalification-wave-01.md @@ -0,0 +1,25 @@ +# Current-source native packet requalification - wave 01 (2026-11-12) + +Owner: opensiro/upstream issue 56. This is technical evidence, NOT a fork write, external PR, maintainer adoption, or VSM grade claim. Frozen candidate batches remain unchanged. + +| Original repository | Current source HEAD | Exact selected patch SHA256 | Fresh source-native replay | Other remaining checks | +|---|---|---|---|---| +| kstawiski/evidence-gated-scientific-agent | 85b396a824dfad6ec0880cbe07d86d3e24920ae8 | 0974cdefa228cfdf37e1b750144e01c269c8f2dee6d9aa8ea01e86dd79b23646 | BEFORE 15 pass/1 fail; AFTER 16 pass/0 fail | Whole uv pytest non-live gate not yet run; fork-local PR absent | +| microsoft/fara | a675d6d61c41c47ae87bacefeab22caad18e3e84 | fce42386bd6b1cf1ea0fed6c22a206589239cdeeb37f53cfaa48eb027da9002e | BEFORE 2 pass/1 fail; AFTER 3 pass/0 fail | Broader first-party tests and Microsoft CLA/policy review not complete; fork-local PR absent | +| ScaleML/AgentSPEX | db99b7b6802fe15ef2997c82ec1d49e5228128f6 | Not selected | Old patch applied to exact source; pytest collection blocked by missing python-dotenv | First-party environment and feature fit | +| howl-anderson/agentsilex | cd529f2838151fd8a4f0d6b7054a45d829b3f78d | Not selected | Old patch applied to exact source; pytest collection blocked by missing litellm | Evaluation environment and broad baseline | +| agentuniverse-ai/agentUniverse | f69b130bacaa784289ec8dc2a9798d122cd8fe5a | Not selected | Old source 114 commits behind, comparison shows no changes to four proposed patch files | Fresh patch replay, native tests, maintainer consultation | + +## Source-native execution receipts + +Executed 2026-11-12 via connected dev-alpha-2 SSH gateway on isolated shallow clones of the original GitHub default branches, Python 3.12.3, pytest 7.4.4, and an isolated Pydantic >=2.11 installation. Authenticated GitHub actor: xLagerFeuer. + +Evidence Bench: original SHA equals the canonical Index review_ref; reviewed_at 2026-10-17; original Apache-2.0 LICENSE and CONTRIBUTING inspected. Current first-party CONTRIBUTING welcomes focused narrow deterministic policy fixes and proposes the broad non-live test suite. Test-only patch against pristine original yielded 15 passes, 1 failing negative test, because status=error counted successful and admitted a failure URL. Exact production patch yielded 16 passes, 0 failures on tests/test_policy.py. Original and patch SHA and git apply --check / diff --check verified. No model or live retrieval performed. + +Fara: original SHA equals the canonical Index review_ref; reviewed_at 2026-10-17; first-party MIT LICENSE inspected, root CONTRIBUTING and common PR templates absent. On pristine original, test-only patch yielded 2 passes and 1 failing mismatch test, because existing checkpoint reused a task with a different task_id. Exact production patch yielded 3 passes and 0 failures on tests/test_run_context_resume_identity.py. Source SHA, exact patch SHA, apply and diff checks verified. No browser, model, or benchmark performed. + +Archived patch bytes are identical to source-only OpenSiro draft PRs 120 and 121, independently re-executed in this session, not accepted automatically from those drafts. No unpublished PR-body draft was copied into Upstream. Existing direct public forks for Evidence Bench and Fara matched original main SHA at preflight. Matching-author third-party external PR search found no published proposal for any of the five, which does not establish global semantic nonduplication. + +Host filesystem usage reached 99 percent (about 1.9 GB free). No first-party broad CI, source-native fork CI, actual fork commit, external PR POST, third-party merger, deployment, or strict assessment-derived causal completion is claimed. + +Next: review this exact technical packet and pass Upstream integrity. On merged main, independently verify effective standing mandates, GitHub actor, source and fork SHAs, license/contributor policy and native completeness before issuing an exact one-use preparation decision. Ineligible cases remain blocked within this fixed five-source wave. diff --git a/scripts/contribution_workspaces.py b/scripts/contribution_workspaces.py index efd0fa4e..5f392681 100644 --- a/scripts/contribution_workspaces.py +++ b/scripts/contribution_workspaces.py @@ -23,9 +23,11 @@ REGISTRY = Path("data/contribution-workspaces/registry.json") PILOTS = Path("data/contribution-workspaces/pilot-sources.json") PR_HISTORY = Path("data/upstream-lifecycle.json") +SELECTED_PACKETS = Path("data/contribution-workspaces/selected-packets.json") FORK_READBACK_DIR = Path("data/contribution-workspaces/fork-readbacks") SOURCE = re.compile(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\Z") COMMIT_SHA = re.compile(r"[0-9a-f]{40}\Z") +PATCH_SHA256 = re.compile(r"[0-9a-f]{64}\Z") LOCAL_HANDOFF = re.compile( r"https://github\.com/opensiro/upstream/blob/(?:main|[0-9a-f]{40})/" r"(pilots/[A-Za-z0-9_.-]+/CONTRIBUTION_HANDOFF\.md)\Z" @@ -149,6 +151,44 @@ def _technical_packet(root, item): return result +def _selected_patch_packets(root): + """Reviewed additions for historically frozen screening packets. + + A selected digest must match actual committed patch bytes. + """ + path = root / SELECTED_PACKETS + if not path.exists(): + return {} + data = read(path) + require(type(data) is dict and set(data) == {"schema_version", "selections"} + and data["schema_version"] == 1 and type(data["selections"]) is list, + "Invalid additive packet selection manifest") + selected = {} + fields = {"source", "candidate_id", "source_sha", "patch_path", + "patch_sha256", "receipt"} + for item in data["selections"]: + require(type(item) is dict and set(item) == fields + and type(item["source"]) is str + and SOURCE.fullmatch(item["source"]) + and type(item["candidate_id"]) is str and item["candidate_id"] + and type(item["source_sha"]) is str + and COMMIT_SHA.fullmatch(item["source_sha"]) + and type(item["patch_sha256"]) is str + and PATCH_SHA256.fullmatch(item["patch_sha256"]) + and type(item["patch_path"]) is str + and type(item["receipt"]) is str, + "Invalid exact selected packet identity, source or digest") + receipt = Path(item["receipt"]) + require(not receipt.is_absolute() and len(receipt.parts) >= 2 + and receipt.parts[0] == "reports" and ".." not in receipt.parts + and receipt.suffix == ".md" and (root / receipt).is_file(), + "Selected packet has no native validation receipt") + pair = (key(item["source"]), item["candidate_id"]) + require(pair not in selected, "Duplicate selected packet") + selected[pair] = item + return selected + + def _dated_fork_readbacks(root): """Validate dated, reviewable GitHub readbacks; never attest live write rights.""" latest = {} @@ -208,6 +248,7 @@ def _dated_fork_readbacks(root): def build(root=ROOT): root = Path(root) fork_readbacks = _dated_fork_readbacks(root) + selected_packets = _selected_patch_packets(root) pool = read(root / POOL) require(pool.get("schema_version") == 1, "Unsupported priority pool") sources = {} @@ -267,19 +308,33 @@ def build(root=ROOT): record["batches"].append(path.relative_to(root).as_posix()) record["latest_stage"] = item["stage"] if item["stage"] in {"AWAITING_EXTERNAL", "CLOSED_CHANGE"}: - # Local artifact references only: an external fork commit is - # never inferred from a handoff or a passing native test. + selected = selected_packets.pop((identity, item["id"]), None) + packet_item = item + if selected is not None: + require(item.get("patch_path") is None + and item.get("patch_sha256") is None + and selected["source_sha"] == item.get("review_ref"), + "Selected packet conflicts with frozen source evidence") + packet_item = {**item, + "patch_path": selected["patch_path"], + "patch_sha256": selected["patch_sha256"]} + require(_technical_packet(root, packet_item)["state"] + == "EXPLICIT_PATCH_HASH_MATCHED", + "Selected patch bytes differ from reviewed digest") + # No fork commit is inferred from a handoff or native test. record["handoffs"].append({ "candidate_id": item["id"], "stage": item["stage"], "origin": item["origin"], "handoff_url": item.get("handoff_url"), - "patch_path": item.get("patch_path"), - "patch_sha256": item.get("patch_sha256"), - "test_patch_path": item.get("test_patch_path"), - "technical_packet": _technical_packet(root, item), + "patch_path": packet_item.get("patch_path"), + "patch_sha256": packet_item.get("patch_sha256"), + "test_patch_path": packet_item.get("test_patch_path"), + "technical_packet": _technical_packet(root, packet_item), }) + require(not selected_packets, "Selected packet has no matching frozen handoff") + # Earlier product-native pilots were outside the five-source assessment # batches, but are equally important worked-source inventory. The explicit # mappings are backed by first-party receipts or internal reviewable PRs. diff --git a/tests/test_contribution_workspaces.py b/tests/test_contribution_workspaces.py index 04661790..82224dba 100644 --- a/tests/test_contribution_workspaces.py +++ b/tests/test_contribution_workspaces.py @@ -109,6 +109,45 @@ def test_local_pilot_artifacts_are_bound_to_registry_without_auto_selection(self self.assertGreaterEqual(counts["technical_packet_states"] ["EXPLICIT_PATCH_HASH_MATCHED"], 10) + def test_selected_legacy_packets_are_exact_and_frozen_batches_unchanged(self): + by_source = {r["source"].casefold(): r for r in workspaces.build()["sources"]} + for source, digest in ( + ("microsoft/fara", "fce42386bd6b1cf1ea0fed6c22a206589239cdeeb37f53cfaa48eb027da9002e"), + ("kstawiski/evidence-gated-scientific-agent", + "0974cdefa228cfdf37e1b750144e01c269c8f2dee6d9aa8ea01e86dd79b23646"), + ): + handoff = by_source[source]["handoffs"][0] + self.assertEqual(handoff["technical_packet"]["state"], + "EXPLICIT_PATCH_HASH_MATCHED") + self.assertEqual(handoff["patch_sha256"], digest) + self.assertEqual(handoff["technical_packet"]["explicit_patch"]["sha256"], digest) + frozen = json.loads((ROOT / workspaces.BATCH_DIR / "2026-10-23-11.json").read_text()) + for item in frozen["candidates"]: + if item["source"].casefold() in ( + "microsoft/fara", "kstawiski/evidence-gated-scientific-agent" + ): + self.assertNotIn("patch_path", item) + self.assertNotIn("patch_sha256", item) + + def test_selected_packet_duplicate_and_missing_receipt_fail_closed(self): + data = json.loads((ROOT / workspaces.SELECTED_PACKETS).read_text()) + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + destination = root / workspaces.SELECTED_PACKETS + destination.parent.mkdir(parents=True) + destination.write_text(json.dumps(data)) + with self.assertRaisesRegex(workspaces.WorkspaceError, + "native validation receipt"): + workspaces._selected_patch_packets(root) + report = root / data["selections"][0]["receipt"] + report.parent.mkdir(parents=True) + report.write_text("verified") + data["selections"].append(dict(data["selections"][0])) + destination.write_text(json.dumps(data)) + with self.assertRaisesRegex(workspaces.WorkspaceError, + "Duplicate selected packet"): + workspaces._selected_patch_packets(root) + def test_generated_registry_is_current_and_check_is_read_only(self): self.assertEqual((ROOT / workspaces.REGISTRY).read_text(encoding="utf-8"), workspaces.encoded(workspaces.build()))