From f141e816bb10c10a895544c4c966420ca717dab8 Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 8 Sep 2026 17:37:29 -0400 Subject: [PATCH 1/6] feat: prepare measured Flow 1.35.0 admission candidate Signed-off-by: abrichr --- .../flow-1.35.0-measured/README.md | 33 ++ .../flow-1.35.0-measured/candidate.json | 175 +++++++++++ .../flow-1.35.0-measured/prepare.py | 286 ++++++++++++++++++ tests/test_measured_flow_candidate.py | 117 +++++++ 4 files changed, 611 insertions(+) create mode 100644 local-candidates/flow-1.35.0-measured/README.md create mode 100644 local-candidates/flow-1.35.0-measured/candidate.json create mode 100644 local-candidates/flow-1.35.0-measured/prepare.py create mode 100644 tests/test_measured_flow_candidate.py diff --git a/local-candidates/flow-1.35.0-measured/README.md b/local-candidates/flow-1.35.0-measured/README.md new file mode 100644 index 0000000..34c78cc --- /dev/null +++ b/local-candidates/flow-1.35.0-measured/README.md @@ -0,0 +1,33 @@ +# Flow 1.35.0 measured admission candidate + +This directory prepares an unsigned release candidate for review. `prepare.py` +checks the published wheel and sdist, their GitHub asset digests, the annotated +tag, and the successful qualification and three-OS lifecycle runs for source +`6d1f80aa4775e6aa398c3251c8d53e7ec61d0c0c`. + +The measured evidence must come from the installed 1.35.0 wheel. The script +checks every retained file against its manifest, rejects repeated trial +identities and reused observations, and derives the campaign totals from the +individual trials. Missing classes, omitted counters, altered files, an +incorrect runtime, or a failed admission condition stop preparation. + +```bash +python local-candidates/flow-1.35.0-measured/prepare.py \ + --measured-manifest /absolute/path/to/measured-manifest.json \ + --out local-candidates/flow-1.35.0-measured/candidate.json +``` + +Without `--measured-manifest`, the output is an `evidence-incomplete` release +inventory. It cannot assert measured acceptance. The tests in +`tests/test_measured_flow_candidate.py` exercise the validator with explicit +test fixtures; they aren't qualification trials. + +The evidence scope is the local synthetic SQLite and pixel fixture. Synthetic +reviewer inputs exercise the real decision APIs. They do not establish customer +qualification, an actual human review, or approval to issue an admission. + +`prepare.py` never reads a signing key or changes a registry. After review, the +existing issuer must bind the exact retained manifest and contract digests, +preserve the previous admission in sequence, and issue the matching signed +objects. The live ledger and its projections require a separate reviewed +change. Flow 1.34.0 remains the published admission until that change succeeds. diff --git a/local-candidates/flow-1.35.0-measured/candidate.json b/local-candidates/flow-1.35.0-measured/candidate.json new file mode 100644 index 0000000..6d617ed --- /dev/null +++ b/local-candidates/flow-1.35.0-measured/candidate.json @@ -0,0 +1,175 @@ +{ + "admission_issued": false, + "canonical_policy_commit": "9b61391713203998e584f4834e4812bb41b40f6e", + "evidence_class": "remote-safe-synthetic", + "proposed_release_identity": { + "channel": "production", + "previous_admission_sha256": "sha256:d2983b86d5428c892421b191f6943a63cf4f9a0b1629e324d099f5df6fa5dcda", + "schema_version": "openadapt.monotonic-production-release/v1", + "sequence": 2 + }, + "release_observation": { + "artifacts": [ + { + "github_url": "https://github.com/OpenAdaptAI/openadapt-flow/releases/download/v1.35.0/openadapt_flow-1.35.0-py3-none-any.whl", + "kind": "python-wheel", + "name": "openadapt_flow-1.35.0-py3-none-any.whl", + "pypi_url": "https://files.pythonhosted.org/packages/09/89/6c00cae30e9a75f5c2473178550012a8afeeab83c9f465e3039b051a2374/openadapt_flow-1.35.0-py3-none-any.whl", + "sha256": "sha256:2bd0122d506aade41a018cdac1f06a139adef7e85d4ca0b186bb56ee94dc3297", + "size_bytes": 2121501 + }, + { + "github_url": "https://github.com/OpenAdaptAI/openadapt-flow/releases/download/v1.35.0/openadapt_flow-1.35.0.tar.gz", + "kind": "python-sdist", + "name": "openadapt_flow-1.35.0.tar.gz", + "pypi_url": "https://files.pythonhosted.org/packages/d8/aa/8086768b5a0df9f901a972d4e2bce829a18a24429d2969bdcda1f0e48b5d/openadapt_flow-1.35.0.tar.gz", + "sha256": "sha256:45668042b346fd40ec881f8d43ed8b395c86aa075729168d4787877e03ee6ef3", + "size_bytes": 21116872 + } + ], + "observed_at": "2026-09-08T21:35:47.008801+00:00", + "release_url": "https://github.com/OpenAdaptAI/openadapt-flow/releases/tag/v1.35.0", + "runs": { + "qualification": { + "conclusion": "success", + "id": 33822314626, + "jobs": [ + { + "conclusion": "success", + "name": "docs-consistency" + }, + { + "conclusion": "success", + "name": "scope" + }, + { + "conclusion": "success", + "name": "windows-mock" + }, + { + "conclusion": "success", + "name": "effectbench-standalone" + }, + { + "conclusion": "success", + "name": "interop-types" + }, + { + "conclusion": "success", + "name": "python-compatibility" + }, + { + "conclusion": "success", + "name": "phi-guard" + }, + { + "conclusion": "success", + "name": "mypy-strict-safety" + }, + { + "conclusion": "success", + "name": "lint" + }, + { + "conclusion": "success", + "name": "linux-atspi-x11" + }, + { + "conclusion": "success", + "name": "test" + }, + { + "conclusion": "success", + "name": "wheel" + }, + { + "conclusion": "success", + "name": "e2e-browser" + }, + { + "conclusion": "success", + "name": "test-matrix (ubuntu-latest, 3.12)" + }, + { + "conclusion": "success", + "name": "test-matrix (ubuntu-latest, 3.11)" + }, + { + "conclusion": "success", + "name": "test-matrix (macos-latest, 3.12)" + }, + { + "conclusion": "success", + "name": "test-matrix (ubuntu-latest, 3.10)" + } + ], + "source_commit": "6d1f80aa4775e6aa398c3251c8d53e7ec61d0c0c", + "url": "https://github.com/OpenAdaptAI/openadapt-flow/actions/runs/33822314626" + }, + "release": { + "conclusion": "success", + "id": 33826859321, + "jobs": [ + { + "conclusion": "success", + "name": "validate-tag" + }, + { + "conclusion": "skipped", + "name": "backfill-github-release" + }, + { + "conclusion": "skipped", + "name": "validate-dispatch" + }, + { + "conclusion": "skipped", + "name": "create-release-tag" + }, + { + "conclusion": "success", + "name": "publish-tag" + } + ], + "source_commit": "6d1f80aa4775e6aa398c3251c8d53e7ec61d0c0c", + "url": "https://github.com/OpenAdaptAI/openadapt-flow/actions/runs/33826859321" + }, + "three_os_lifecycle": { + "conclusion": "success", + "id": 33822325382, + "jobs": [ + { + "conclusion": "success", + "name": "lifecycle (macos-latest)" + }, + { + "conclusion": "success", + "name": "lifecycle (ubuntu-latest)" + }, + { + "conclusion": "success", + "name": "lifecycle (windows-latest)" + } + ], + "source_commit": "6d1f80aa4775e6aa398c3251c8d53e7ec61d0c0c", + "url": "https://github.com/OpenAdaptAI/openadapt-flow/actions/runs/33822325382" + } + }, + "source_commit": "6d1f80aa4775e6aa398c3251c8d53e7ec61d0c0c", + "tag": "v1.35.0", + "tag_object": "4e851c65e8fbfd676c2851360f567e2bb7af4945", + "version": "1.35.0" + }, + "required_campaign_classes": [ + "healthy", + "safe_halt", + "idempotency_replay", + "uncertain_delivery", + "declared_attended", + "governed_repair" + ], + "schema_version": "openadapt.unsigned-measured-release-candidate/v1", + "scope": "Executed local synthetic SQLite and pixel fixture; synthetic reviewer inputs; no customer qualification or actual human approval.", + "state": "evidence-incomplete", + "target": "flow" +} diff --git a/local-candidates/flow-1.35.0-measured/prepare.py b/local-candidates/flow-1.35.0-measured/prepare.py new file mode 100644 index 0000000..399a6d2 --- /dev/null +++ b/local-candidates/flow-1.35.0-measured/prepare.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +"""Prepare an unsigned Flow 1.35.0 admission candidate from measured trials. + +No key access, signatures, registry writes, or publication occur here. The +operator must review the exact candidate before the existing issuer uses it. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import subprocess +import sys +from collections import Counter, defaultdict +from datetime import datetime, timezone +from pathlib import Path +from urllib.request import urlopen + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "scripts")) +import production_trust as trust # noqa: E402 + +VERSION = "1.35.0" +SOURCE = "6d1f80aa4775e6aa398c3251c8d53e7ec61d0c0c" +REPOSITORY = "OpenAdaptAI/openadapt-flow" +RUNS = { + "release": 33826859321, + "qualification": 33822314626, + "three_os_lifecycle": 33822325382, +} +DERIVED_FIELDS = { + "task_condition_cell_count", + "minimum_trials_per_cell", + "observed_trial_count", +} +COUNTERS = trust.CAMPAIGN_COUNT_FIELDS - DERIVED_FIELDS + + +def sha(raw: bytes) -> str: + return "sha256:" + hashlib.sha256(raw).hexdigest() + + +def read_json(path: Path): + return json.loads(path.read_bytes()) + + +def gh(path: str): + return json.loads(subprocess.check_output(["gh", "api", path], text=True)) + + +def observe_release() -> dict: + with urlopen( + f"https://pypi.org/pypi/openadapt-flow/{VERSION}/json", timeout=30 + ) as response: + pypi = json.load(response) + files = pypi["urls"] + if len(files) != 2 or {f["packagetype"] for f in files} != {"bdist_wheel", "sdist"}: + raise ValueError("the release must contain exactly one wheel and one sdist") + release = gh(f"repos/{REPOSITORY}/releases/tags/v{VERSION}") + if release["draft"] or release["prerelease"]: + raise ValueError("the GitHub release is not a stable public release") + ref = gh(f"repos/{REPOSITORY}/git/ref/tags/v{VERSION}")["object"] + if ref["type"] != "tag": + raise ValueError("expected the annotated release tag") + tag = gh(f"repos/{REPOSITORY}/git/tags/{ref['sha']}") + if tag["object"] != { + "sha": SOURCE, + "type": "commit", + "url": f"https://api.github.com/repos/{REPOSITORY}/git/commits/{SOURCE}", + }: + raise ValueError("the release tag source changed") + artifacts = [] + for item in sorted(files, key=lambda f: f["filename"]): + if item["yanked"]: + raise ValueError("a release artifact is yanked") + asset = [a for a in release["assets"] if a["name"] == item["filename"]] + expected_digest = "sha256:" + item["digests"]["sha256"] + if ( + len(asset) != 1 + or asset[0]["state"] != "uploaded" + or asset[0]["size"] != item["size"] + or asset[0].get("digest") != expected_digest + ): + raise ValueError("GitHub and PyPI artifact inventory differs") + artifacts.append( + { + "name": item["filename"], + "sha256": expected_digest, + "size_bytes": item["size"], + "pypi_url": item["url"], + "github_url": asset[0]["browser_download_url"], + "kind": "python-wheel" + if item["packagetype"] == "bdist_wheel" + else "python-sdist", + } + ) + runs = {} + for name, run_id in RUNS.items(): + run = gh(f"repos/{REPOSITORY}/actions/runs/{run_id}") + if ( + run["head_sha"] != SOURCE + or run["status"] != "completed" + or run["conclusion"] != "success" + ): + raise ValueError(f"the exact-source {name} run did not pass") + jobs = gh(f"repos/{REPOSITORY}/actions/runs/{run_id}/jobs?per_page=100") + if jobs["total_count"] != len(jobs["jobs"]) or any( + j["conclusion"] not in {"success", "skipped"} for j in jobs["jobs"] + ): + raise ValueError(f"the {name} job inventory is incomplete or failed") + if name != "release" and any( + j["conclusion"] != "success" for j in jobs["jobs"] + ): + raise ValueError( + "every exact-release qualification job must execute successfully" + ) + runs[name] = { + "id": run_id, + "url": run["html_url"], + "source_commit": run["head_sha"], + "conclusion": run["conclusion"], + "jobs": [ + {"name": j["name"], "conclusion": j["conclusion"]} for j in jobs["jobs"] + ], + } + return { + "observed_at": datetime.now(timezone.utc).isoformat(), + "version": VERSION, + "source_commit": SOURCE, + "tag": f"v{VERSION}", + "tag_object": ref["sha"], + "release_url": release["html_url"], + "artifacts": artifacts, + "runs": runs, + } + + +def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: + root = manifest_path.resolve().parent + raw = manifest_path.read_bytes() + document = json.loads(raw) + if ( + document["schema_version"] != "openadapt.measured-release-evidence/v1" + or document["evidence_class"] != "remote-safe-synthetic" + ): + raise ValueError("wrong measured evidence schema or class") + if document["runtime"] != {"version": VERSION, "wheel_sha256": wheel_digest}: + raise ValueError("measured runtime does not match the published wheel") + inventory = {} + for item in document["artifacts"]: + relative = Path(item["path"]) + path = (root / relative).resolve() + if ( + relative.is_absolute() + or ".." in relative.parts + or not path.is_relative_to(root) + or (root / relative).is_symlink() + ): + raise ValueError("evidence artifact path escapes its manifest") + data = path.read_bytes() + if len(data) != item["size_bytes"] or sha(data) != item["sha256"]: + raise ValueError(f"artifact bytes differ: {relative}") + if str(relative) in inventory: + raise ValueError("duplicate evidence artifact path") + inventory[str(relative)] = item + reference_counts = Counter( + reference["sha256"] + for trial in document["trials"] + for reference in {item["sha256"]: item for item in trial["artifacts"]}.values() + ) + groups = defaultdict(list) + seen = set() + for trial in document["trials"]: + key = (trial["class"], trial["task_id"], trial["condition"], trial["trial"]) + if ( + key in seen + or trial["class"] not in trust.CAMPAIGN_CLASSES + or trial["runtime_version"] != VERSION + ): + raise ValueError("duplicate, unknown-class, or wrong-runtime trial") + seen.add(key) + if not trial["artifacts"] or not any( + reference_counts[reference["sha256"]] == 1 + for reference in trial["artifacts"] + ): + raise ValueError("a measured trial must retain unique observed evidence") + for reference in trial["artifacts"]: + if ( + inventory.get(reference["path"], {}).get("sha256") + != reference["sha256"] + ): + raise ValueError("trial evidence is absent from the verified inventory") + if set(trial["counters"]) != COUNTERS: + raise ValueError( + "every trial must explicitly observe every outcome counter" + ) + if any( + isinstance(n, bool) or not isinstance(n, int) or n < 0 + for n in trial["counters"].values() + ): + raise ValueError("trial counters must be nonnegative integers") + groups[trial["class"]].append(trial) + summary = {} + for name in trust.CAMPAIGN_CLASSES: + trials = groups[name] + if not trials: + raise ValueError(f"measured trials are missing for {name}") + cells = Counter((t["task_id"], t["condition"]) for t in trials) + counts = {key: sum(t["counters"][key] for t in trials) for key in COUNTERS} + counts.update( + task_condition_cell_count=len(cells), + minimum_trials_per_cell=min(cells.values()), + observed_trial_count=len(trials), + ) + summary[name] = counts + trust.validate_campaign_summary(summary) + return summary, { + "manifest_sha256": sha(raw), + "artifact_count": len(inventory), + "trial_count": len(seen), + "task_count": len({t["task_id"] for t in document["trials"]}), + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--measured-manifest", type=Path) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + observation = observe_release() + wheel = next(a for a in observation["artifacts"] if a["kind"] == "python-wheel") + ledger = read_json(ROOT / "production-lifecycle-admissions.json") + previous = [] + for reference in ledger["admissions"]: + raw = (ROOT / reference["object_path"]).read_bytes() + if sha(raw) != reference["object_sha256"]: + raise ValueError("previous admission bytes differ from the ledger") + admission = json.loads(raw) + if admission["target"] == "flow": + previous.append(admission) + if not previous: + raise ValueError("previous Flow admission is absent") + last = max(previous, key=lambda a: a["release_identity"]["sequence"]) + candidate = { + "schema_version": "openadapt.unsigned-measured-release-candidate/v1", + "target": "flow", + "evidence_class": "remote-safe-synthetic", + "state": "evidence-incomplete", + "admission_issued": False, + "proposed_release_identity": { + "schema_version": "openadapt.monotonic-production-release/v1", + "channel": "production", + "sequence": last["release_identity"]["sequence"] + 1, + "previous_admission_sha256": last["admission_id_sha256"], + }, + "canonical_policy_commit": subprocess.check_output( + ["git", "rev-parse", "origin/main"], cwd=ROOT, text=True + ).strip(), + "scope": "Executed local synthetic SQLite and pixel fixture; synthetic reviewer inputs; no customer qualification or actual human approval.", + "release_observation": observation, + "required_campaign_classes": list(trust.CAMPAIGN_CLASSES), + } + if args.measured_manifest: + summary, measured = summarize(args.measured_manifest, wheel["sha256"]) + candidate.update( + state="ready-for-review", + campaign_summary=summary, + measured_evidence=measured, + ) + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(json.dumps(candidate, indent=2, sort_keys=True) + "\n") + print( + json.dumps( + { + "state": candidate["state"], + "admission_issued": False, + "candidate": str(args.out), + } + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_measured_flow_candidate.py b/tests/test_measured_flow_candidate.py new file mode 100644 index 0000000..865778a --- /dev/null +++ b/tests/test_measured_flow_candidate.py @@ -0,0 +1,117 @@ +"""The review candidate binds counted trials to exact retained bytes.""" + +import importlib.util +import json +from pathlib import Path + +import pytest + +_PATH = ( + Path(__file__).resolve().parents[1] + / "local-candidates/flow-1.35.0-measured/prepare.py" +) +_SPEC = importlib.util.spec_from_file_location("measured_flow_candidate", _PATH) +assert _SPEC and _SPEC.loader +candidate = importlib.util.module_from_spec(_SPEC) +_SPEC.loader.exec_module(candidate) +WHEEL = "sha256:" + "a" * 64 + + +def manifest(tmp_path): + artifact = tmp_path / "observed.json" + artifact.write_text('{"fixture":true}\n') + digest = candidate.sha(artifact.read_bytes()) + trials = [] + artifacts = [] + for name in candidate.trust.CAMPAIGN_CLASSES: + counters = dict.fromkeys(candidate.COUNTERS, 0) + if name == "uncertain_delivery": + counters["reconciliation_required_count"] = 1 + if name == "declared_attended": + counters["authenticated_bound_decision_count"] = 1 + counters["live_target_revalidation_count"] = 1 + if name == "governed_repair": + for field in ( + "policy_approved_repair_count", + "approved_repair_count", + "retained_repair_evidence_count", + "live_target_revalidation_count", + ): + counters[field] = 1 + for trial in range(3): + artifact = tmp_path / f"{name}-{trial}.json" + artifact.write_text( + json.dumps({"fixture": True, "class": name, "trial": trial}) + ) + digest = candidate.sha(artifact.read_bytes()) + artifacts.append( + { + "path": artifact.name, + "sha256": digest, + "size_bytes": artifact.stat().st_size, + } + ) + trials.append( + { + "class": name, + "task_id": "test-fixture", + "condition": "one", + "trial": trial, + "runtime_version": candidate.VERSION, + "artifacts": [{"path": artifact.name, "sha256": digest}], + "counters": counters.copy(), + } + ) + value = { + "schema_version": "openadapt.measured-release-evidence/v1", + "evidence_class": "remote-safe-synthetic", + "runtime": {"version": candidate.VERSION, "wheel_sha256": WHEEL}, + "artifacts": artifacts, + "trials": trials, + } + path = tmp_path / "manifest.json" + path.write_text(json.dumps(value)) + return path, value + + +def test_counts_derive_from_unique_observed_trials(tmp_path): + path, _ = manifest(tmp_path) + summary, evidence = candidate.summarize(path, WHEEL) + assert evidence["trial_count"] == 18 + assert evidence["manifest_sha256"] == candidate.sha(path.read_bytes()) + assert summary["governed_repair"]["approved_repair_count"] == 3 + assert summary["healthy"]["minimum_trials_per_cell"] == 3 + + +@pytest.mark.parametrize( + "mutation", + [ + lambda m: m["trials"].pop(), + lambda m: m["trials"].append(m["trials"][0]), + lambda m: m["trials"][0]["counters"].pop("silent_incorrect_success_count"), + lambda m: m["trials"][0]["counters"].update(silent_incorrect_success_count=1), + lambda m: m["runtime"].update(wheel_sha256="sha256:" + "b" * 64), + lambda m: m["trials"][0]["artifacts"].clear(), + ], +) +def test_incomplete_or_unsafe_evidence_refuses(tmp_path, mutation): + path, value = manifest(tmp_path) + mutation(value) + path.write_text(json.dumps(value)) + with pytest.raises((ValueError, candidate.trust.TrustError)): + candidate.summarize(path, WHEEL) + + +def test_changed_artifact_bytes_refuse(tmp_path): + path, _ = manifest(tmp_path) + (tmp_path / "healthy-0.json").write_text("changed") + with pytest.raises(ValueError, match="artifact bytes differ"): + candidate.summarize(path, WHEEL) + + +def test_reused_observation_cannot_count_as_another_trial(tmp_path): + path, value = manifest(tmp_path) + value["trials"][1]["artifacts"] = value["trials"][0]["artifacts"] + path.write_text(json.dumps(value)) + with pytest.raises(ValueError, match="unique observed evidence"): + candidate.summarize(path, WHEEL) From bd9512a64f5615452a0114f704feaa1777c82546 Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 8 Sep 2026 17:44:39 -0400 Subject: [PATCH 2/6] fix: retain measured admission refusals and integration gaps Signed-off-by: abrichr --- .../flow-1.35.0-measured/README.md | 5 +++ .../__pycache__/prepare.cpython-312.pyc | Bin 0 -> 17212 bytes .../flow-1.35.0-measured/candidate.json | 27 ++++++++++++-- .../flow-1.35.0-measured/prepare.py | 34 +++++++++++++----- tests/test_measured_flow_candidate.py | 29 +++++++++++++++ 5 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 local-candidates/flow-1.35.0-measured/__pycache__/prepare.cpython-312.pyc diff --git a/local-candidates/flow-1.35.0-measured/README.md b/local-candidates/flow-1.35.0-measured/README.md index 34c78cc..0c863c0 100644 --- a/local-candidates/flow-1.35.0-measured/README.md +++ b/local-candidates/flow-1.35.0-measured/README.md @@ -1,5 +1,10 @@ # Flow 1.35.0 measured admission candidate +The retained 1.35.0 evidence refuses admission. The default attended continuation +omits its execution target, and the repair campaign cannot qualify the encrypted +anchor path. Each gap has three retained reproductions. The 33 base-condition +trials and three idempotency trials passed their expected outcomes. + This directory prepares an unsigned release candidate for review. `prepare.py` checks the published wheel and sdist, their GitHub asset digests, the annotated tag, and the successful qualification and three-OS lifecycle runs for source diff --git a/local-candidates/flow-1.35.0-measured/__pycache__/prepare.cpython-312.pyc b/local-candidates/flow-1.35.0-measured/__pycache__/prepare.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de1b0b3221eb876a85e1fb26e0d0b519ac8b424c GIT binary patch literal 17212 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 literal 0 HcmV?d00001 diff --git a/local-candidates/flow-1.35.0-measured/candidate.json b/local-candidates/flow-1.35.0-measured/candidate.json index 6d617ed..cbd1b94 100644 --- a/local-candidates/flow-1.35.0-measured/candidate.json +++ b/local-candidates/flow-1.35.0-measured/candidate.json @@ -2,6 +2,26 @@ "admission_issued": false, "canonical_policy_commit": "9b61391713203998e584f4834e4812bb41b40f6e", "evidence_class": "remote-safe-synthetic", + "limitations": [ + { + "detail": "BoundAttendedExecutor continuation omits execution_target_kind. The qualified default continuation cannot observe the environment and refuses after successful live revalidation.", + "id": "attended-resume-target-kind", + "status": "observed integration failure" + }, + { + "detail": "The repair campaign reads plaintext template/context inputs. It fails campaign proof for the actual encrypted, template-armed OCR repair candidate and refuses approval.", + "id": "encrypted-repair-anchor", + "status": "observed integration failure" + } + ], + "measured_manifest_sha256": "sha256:8f98f2499059d37ff580f6abda1afb741ceb684d8400780ec899ca104467fbd4", + "measured_scope": { + "application": "Pillow synthetic clinic fixture", + "operator_principal": "Explicit synthetic local protocol inputs; no real human/authentication provider proof", + "required_path": "Default real attended and encrypted repair paths; added deployment adapters/plaintext diagnostics do not substitute for those paths", + "runtime": "Exact installed unmodified Flow 1.35.0 wheel", + "system_of_record": "Local SQLite" + }, "proposed_release_identity": { "channel": "production", "previous_admission_sha256": "sha256:d2983b86d5428c892421b191f6943a63cf4f9a0b1629e324d099f5df6fa5dcda", @@ -27,7 +47,7 @@ "size_bytes": 21116872 } ], - "observed_at": "2026-09-08T21:35:47.008801+00:00", + "observed_at": "2026-09-08T21:44:03.027630+00:00", "release_url": "https://github.com/OpenAdaptAI/openadapt-flow/releases/tag/v1.35.0", "runs": { "qualification": { @@ -171,5 +191,8 @@ "schema_version": "openadapt.unsigned-measured-release-candidate/v1", "scope": "Executed local synthetic SQLite and pixel fixture; synthetic reviewer inputs; no customer qualification or actual human approval.", "state": "evidence-incomplete", - "target": "flow" + "target": "flow", + "validation_errors": [ + "measured trials are missing for declared_attended, governed_repair" + ] } diff --git a/local-candidates/flow-1.35.0-measured/prepare.py b/local-candidates/flow-1.35.0-measured/prepare.py index 399a6d2..04b3a70 100644 --- a/local-candidates/flow-1.35.0-measured/prepare.py +++ b/local-candidates/flow-1.35.0-measured/prepare.py @@ -201,11 +201,12 @@ def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: ): raise ValueError("trial counters must be nonnegative integers") groups[trial["class"]].append(trial) + missing = [name for name in trust.CAMPAIGN_CLASSES if not groups[name]] + if missing: + raise ValueError("measured trials are missing for " + ", ".join(missing)) summary = {} for name in trust.CAMPAIGN_CLASSES: trials = groups[name] - if not trials: - raise ValueError(f"measured trials are missing for {name}") cells = Counter((t["task_id"], t["condition"]) for t in trials) counts = {key: sum(t["counters"][key] for t in trials) for key in COUNTERS} counts.update( @@ -262,12 +263,27 @@ def main() -> int: "required_campaign_classes": list(trust.CAMPAIGN_CLASSES), } if args.measured_manifest: - summary, measured = summarize(args.measured_manifest, wheel["sha256"]) - candidate.update( - state="ready-for-review", - campaign_summary=summary, - measured_evidence=measured, - ) + candidate["measured_manifest_sha256"] = sha(args.measured_manifest.read_bytes()) + measured_document = read_json(args.measured_manifest) + candidate["measured_scope"] = measured_document.get("scope") + candidate["limitations"] = measured_document.get("limitations", []) + try: + summary, measured = summarize(args.measured_manifest, wheel["sha256"]) + except (ValueError, trust.TrustError) as exc: + candidate["validation_errors"] = [str(exc)] + else: + if measured_document.get("candidate_ready") is not True: + candidate["validation_errors"] = [ + "measured evidence declares unresolved admission requirements" + ] + elif not candidate["measured_scope"]: + candidate["validation_errors"] = ["measured scope must be explicit"] + else: + candidate.update( + state="ready-for-review", + campaign_summary=summary, + measured_evidence=measured, + ) args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text(json.dumps(candidate, indent=2, sort_keys=True) + "\n") print( @@ -279,7 +295,7 @@ def main() -> int: } ) ) - return 0 + return 1 if candidate.get("validation_errors") else 0 if __name__ == "__main__": diff --git a/tests/test_measured_flow_candidate.py b/tests/test_measured_flow_candidate.py index 865778a..722c0c0 100644 --- a/tests/test_measured_flow_candidate.py +++ b/tests/test_measured_flow_candidate.py @@ -115,3 +115,32 @@ def test_reused_observation_cannot_count_as_another_trial(tmp_path): path.write_text(json.dumps(value)) with pytest.raises(ValueError, match="unique observed evidence"): candidate.summarize(path, WHEEL) + + +def test_incomplete_input_writes_refusal_candidate(tmp_path, monkeypatch): + path, value = manifest(tmp_path) + value["scope"] = "Explicit test fixture; no execution claim" + value["limitations"] = ["Two classes were not measured"] + value["trials"] = [ + t + for t in value["trials"] + if t["class"] not in {"declared_attended", "governed_repair"} + ] + path.write_text(json.dumps(value)) + monkeypatch.setattr( + candidate, + "observe_release", + lambda: {"artifacts": [{"kind": "python-wheel", "sha256": WHEEL}]}, + ) + output = tmp_path / "candidate.json" + monkeypatch.setattr( + candidate.sys, + "argv", + ["prepare.py", "--measured-manifest", str(path), "--out", str(output)], + ) + assert candidate.main() == 1 + result = json.loads(output.read_text()) + assert result["state"] == "evidence-incomplete" + assert result["admission_issued"] is False + assert "declared_attended, governed_repair" in result["validation_errors"][0] + assert result["limitations"] == value["limitations"] From dda3c9f574f588a46d0be4bbfad89ea3fab80ad5 Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 8 Sep 2026 17:44:58 -0400 Subject: [PATCH 3/6] chore: exclude generated candidate bytecode Signed-off-by: abrichr --- .../flow-1.35.0-measured/.gitignore | 1 + .../__pycache__/prepare.cpython-312.pyc | Bin 17212 -> 0 bytes 2 files changed, 1 insertion(+) create mode 100644 local-candidates/flow-1.35.0-measured/.gitignore delete mode 100644 local-candidates/flow-1.35.0-measured/__pycache__/prepare.cpython-312.pyc diff --git a/local-candidates/flow-1.35.0-measured/.gitignore b/local-candidates/flow-1.35.0-measured/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/local-candidates/flow-1.35.0-measured/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/local-candidates/flow-1.35.0-measured/__pycache__/prepare.cpython-312.pyc b/local-candidates/flow-1.35.0-measured/__pycache__/prepare.cpython-312.pyc deleted file mode 100644 index de1b0b3221eb876a85e1fb26e0d0b519ac8b424c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17212 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 From b0088dfed3679f5f9e048651376e38898cfb8dbf Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 8 Sep 2026 18:03:44 -0400 Subject: [PATCH 4/6] fix: bind measured trials to native outcomes and installed wheel proof Signed-off-by: abrichr --- .../flow-1.35.0-measured/README.md | 38 ++ .../flow-1.35.0-measured/candidate.json | 4 +- .../flow-1.35.0-measured/prepare.py | 239 +++++++++++- tests/test_measured_flow_candidate.py | 352 ++++++++++++++++-- 4 files changed, 596 insertions(+), 37 deletions(-) diff --git a/local-candidates/flow-1.35.0-measured/README.md b/local-candidates/flow-1.35.0-measured/README.md index 0c863c0..bcb4640 100644 --- a/local-candidates/flow-1.35.0-measured/README.md +++ b/local-candidates/flow-1.35.0-measured/README.md @@ -36,3 +36,41 @@ existing issuer must bind the exact retained manifest and contract digests, preserve the previous admission in sequence, and issue the matching signed objects. The live ledger and its projections require a separate reviewed change. Flow 1.34.0 remains the published admission until that change succeeds. + +The corrected successor must use a new immutable version. To inspect an +unpublished 1.35.1 wheel and sdist with their measured evidence: + +```bash +python local-candidates/flow-1.35.0-measured/prepare.py \ + --unpublished-wheel /absolute/path/to/openadapt_flow-1.35.1-py3-none-any.whl \ + --unpublished-sdist /absolute/path/to/openadapt_flow-1.35.1.tar.gz \ + --source-commit EXACT_BUILD_COMMIT \ + --measured-manifest /absolute/path/to/1.35.1/evidence-manifest.json \ + --out local-candidates/flow-1.35.1-measured/candidate.json +``` + +This mode verifies the local archive metadata and hashes. The source commit +comes from the build record and requires review against the retained build +provenance. Even complete measured evidence produces only +`ready-for-release-review`. It records publication as false and retains the +required merge, protected-main qualification, three-OS lifecycle, immutable +publication, artifact parity, and admission steps. The 1.35.0 refusal stays +separate from the successor's evidence. + +For a complete candidate, every counted trial must retain a normalized +observation with the same identity and counters as its manifest entry. The +verifier reads the referenced native reports and checks their outcome, success +flag, and model-call counts. Each uncertain-delivery, attended, and repair +trial must satisfy its own event requirements. Events from one trial cannot +satisfy another trial's requirement. + +Attended acceptance requires a native `VERIFIED` effect report. A receipt that +only reports decision completion cannot replace that effect proof. The wheel +check compares every inventoried runtime member with the retained installation +proof and rejects omitted members or extra installed files. + +These checks establish consistency among the retained bytes. They don't prove +that an execution took place. Reviewers must inspect the retained measurement +code, installation provenance, and independent oracle references before they +accept the observation. The public verifier does not contain private oracle +recipes or grant authority to its test fixtures. diff --git a/local-candidates/flow-1.35.0-measured/candidate.json b/local-candidates/flow-1.35.0-measured/candidate.json index cbd1b94..8210476 100644 --- a/local-candidates/flow-1.35.0-measured/candidate.json +++ b/local-candidates/flow-1.35.0-measured/candidate.json @@ -14,7 +14,7 @@ "status": "observed integration failure" } ], - "measured_manifest_sha256": "sha256:8f98f2499059d37ff580f6abda1afb741ceb684d8400780ec899ca104467fbd4", + "measured_manifest_sha256": "sha256:6b7ee4704e42776f707f4b176996b08ffa6bfda1501bc68b65faf7b3dbb59cff", "measured_scope": { "application": "Pillow synthetic clinic fixture", "operator_principal": "Explicit synthetic local protocol inputs; no real human/authentication provider proof", @@ -47,7 +47,7 @@ "size_bytes": 21116872 } ], - "observed_at": "2026-09-08T21:44:03.027630+00:00", + "observed_at": "2026-09-08T21:46:10.121623+00:00", "release_url": "https://github.com/OpenAdaptAI/openadapt-flow/releases/tag/v1.35.0", "runs": { "qualification": { diff --git a/local-candidates/flow-1.35.0-measured/prepare.py b/local-candidates/flow-1.35.0-measured/prepare.py index 04b3a70..f9efd4b 100644 --- a/local-candidates/flow-1.35.0-measured/prepare.py +++ b/local-candidates/flow-1.35.0-measured/prepare.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Prepare an unsigned Flow 1.35.0 admission candidate from measured trials. +"""Prepare an unsigned Flow release candidate from measured trials. No key access, signatures, registry writes, or publication occur here. The operator must review the exact candidate before the existing issuer uses it. @@ -11,6 +11,9 @@ import hashlib import json import subprocess +import tarfile +import zipfile +from email.parser import BytesParser import sys from collections import Counter, defaultdict from datetime import datetime, timezone @@ -136,7 +139,145 @@ def observe_release() -> dict: } -def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: +def verify_trial_observation( + trial: dict, + inventory: dict, + root: Path, + version: str, + wheel_digest: str, + proofs: set[str], +) -> None: + """Cross-check normalized counters against their retained native reports. + + Hashes establish byte identity. They do not prove that a process executed; + review of the retained measurement code and provenance remains required. + """ + + def referenced(reference: dict) -> Path: + if ( + not isinstance(reference.get("path"), str) + or reference["path"] not in inventory + or inventory[reference["path"]]["sha256"] != reference.get("sha256") + ): + raise ValueError("observation reference is absent from verified inventory") + return root / reference["path"] + + observed = read_json(referenced(trial.get("observation", {}))) + if observed.get("schema_version") != "openadapt.observed-release-trial/v1": + raise ValueError("wrong observed trial schema") + for field in ("class", "task_id", "condition", "trial", "counters"): + if observed.get(field) != trial[field]: + raise ValueError(f"retained observation differs from trial {field}") + runtime = observed.get("runtime", {}) + if runtime.get("version") != version or runtime.get("wheel_sha256") != wheel_digest: + raise ValueError("observed runtime identity differs") + proof_reference = runtime.get("installed_distribution_proof", {}) + proof_path = referenced(proof_reference) + if proof_reference["sha256"] not in proofs: + proof = read_json(proof_path) + if ( + proof.get("schema_version") != "openadapt.installed-distribution-proof/v1" + or proof.get("distribution") != "openadapt-flow" + or proof.get("version") != version + or proof.get("all_members_match") is not True + or proof.get("installed_extra_files") != [] + ): + raise ValueError("installed distribution proof is incomplete or mismatched") + wheel_reference = proof.get("wheel", {}) + wheel_path = referenced(wheel_reference) + if wheel_reference.get("sha256") != wheel_digest: + raise ValueError("installed proof names another wheel") + if wheel_reference.get("size_bytes") != wheel_path.stat().st_size: + raise ValueError("installed proof wheel size differs") + members = proof.get("members", []) + indexed = {m["path"]: m for m in members} + if len(indexed) != len(members): + raise ValueError("duplicate installed member proof") + with zipfile.ZipFile(wheel_path) as archive: + names = [ + n + for n in archive.namelist() + if n.startswith("openadapt_flow/") and not n.endswith("/") + ] + if not names or len(names) != len(set(names)) or set(names) != set(indexed): + raise ValueError( + "installed member proof does not cover the exact wheel" + ) + for name in names: + raw = archive.read(name) + item = indexed[name] + if ( + item.get("size_bytes") != len(raw) + or item.get("wheel_sha256") != sha(raw) + or item.get("installed_sha256") != sha(raw) + ): + raise ValueError("installed member differs from candidate wheel") + proofs.add(proof_reference["sha256"]) + + reports = observed.get("reports", []) + if not reports or len({r["path"] for r in reports}) != len(reports): + raise ValueError("observed trial needs distinct retained native reports") + primary = [] + model_calls = 0 + for reference in reports: + report = read_json(referenced(reference)) + for field in ("success", "transaction_outcome", "model_calls"): + if field not in report or report[field] != reference.get(field): + raise ValueError( + f"retained native report differs from observed {field}" + ) + if not isinstance(report["success"], bool): + raise ValueError("native report success must be a boolean") + count = report["model_calls"] + if isinstance(count, bool) or not isinstance(count, int) or count < 0: + raise ValueError("native model calls must be observed nonnegative integers") + model_calls += count + if reference.get("role") == "primary": + primary.append(report) + if len(primary) != 1 or model_calls != trial["counters"]["model_call_count"]: + raise ValueError("primary report or model call total differs") + final = primary[0] + if trial["class"] == "uncertain_delivery": + expected = ( + not final["success"] + and final["transaction_outcome"] == "RECONCILIATION_REQUIRED" + ) + elif trial["class"] == "safe_halt": + expected = not final["success"] and final["transaction_outcome"] != "VERIFIED" + else: + expected = final["success"] and final["transaction_outcome"] == "VERIFIED" + if not expected: + raise ValueError("primary native outcome does not satisfy its measured class") + references = observed.get("verification_references", []) + if not references: + raise ValueError("observed trial has no retained verification references") + for reference in references: + referenced(reference) + + +def validate_trial_events(trial: dict) -> None: + required = { + "uncertain_delivery": ("reconciliation_required_count",), + "declared_attended": ( + "authenticated_bound_decision_count", + "live_target_revalidation_count", + ), + "governed_repair": ( + "policy_approved_repair_count", + "approved_repair_count", + "retained_repair_evidence_count", + "live_target_revalidation_count", + ), + } + if any(trial["counters"][field] != 1 for field in required.get(trial["class"], ())): + raise ValueError( + "each trial must satisfy its own required class events exactly once" + ) + + +def summarize( + manifest_path: Path, wheel_digest: str, *, version: str = VERSION +) -> tuple[dict, dict]: root = manifest_path.resolve().parent raw = manifest_path.read_bytes() document = json.loads(raw) @@ -145,8 +286,8 @@ def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: or document["evidence_class"] != "remote-safe-synthetic" ): raise ValueError("wrong measured evidence schema or class") - if document["runtime"] != {"version": VERSION, "wheel_sha256": wheel_digest}: - raise ValueError("measured runtime does not match the published wheel") + if document["runtime"] != {"version": version, "wheel_sha256": wheel_digest}: + raise ValueError("measured runtime does not match the candidate wheel") inventory = {} for item in document["artifacts"]: relative = Path(item["path"]) @@ -176,7 +317,7 @@ def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: if ( key in seen or trial["class"] not in trust.CAMPAIGN_CLASSES - or trial["runtime_version"] != VERSION + or trial["runtime_version"] != version ): raise ValueError("duplicate, unknown-class, or wrong-runtime trial") seen.add(key) @@ -204,6 +345,19 @@ def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: missing = [name for name in trust.CAMPAIGN_CLASSES if not groups[name]] if missing: raise ValueError("measured trials are missing for " + ", ".join(missing)) + normalizer = document.get("normalizer", {}) + if ( + not isinstance(normalizer.get("path"), str) + or not normalizer["path"] + or normalizer["path"] not in inventory + or not isinstance(normalizer.get("sha256"), str) + or inventory[normalizer["path"]]["sha256"] != normalizer["sha256"] + ): + raise ValueError("measurement normalizer is absent from verified inventory") + proofs: set[str] = set() + for trial in document["trials"]: + validate_trial_events(trial) + verify_trial_observation(trial, inventory, root, version, wheel_digest, proofs) summary = {} for name in trust.CAMPAIGN_CLASSES: trials = groups[name] @@ -224,12 +378,75 @@ def summarize(manifest_path: Path, wheel_digest: str) -> tuple[dict, dict]: } +def observe_unpublished(wheel: Path, sdist: Path, source: str) -> dict: + if len(source) != 40 or any(c not in "0123456789abcdef" for c in source): + raise ValueError("candidate source must be an exact commit SHA") + with zipfile.ZipFile(wheel) as archive: + metadata_paths = [ + n for n in archive.namelist() if n.endswith(".dist-info/METADATA") + ] + if len(metadata_paths) != 1: + raise ValueError("wheel metadata is ambiguous") + wheel_metadata = BytesParser().parsebytes(archive.read(metadata_paths[0])) + with tarfile.open(sdist, "r:gz") as archive: + metadata_paths = [ + m + for m in archive.getmembers() + if m.name.count("/") == 1 and m.name.endswith("/PKG-INFO") + ] + if len(metadata_paths) != 1 or not metadata_paths[0].isfile(): + raise ValueError("sdist metadata is ambiguous") + stream = archive.extractfile(metadata_paths[0]) + assert stream is not None + sdist_metadata = BytesParser().parsebytes(stream.read()) + version = wheel_metadata["Version"] + if ( + wheel_metadata["Name"] != "openadapt-flow" + or sdist_metadata["Name"] != "openadapt-flow" + or sdist_metadata["Version"] != version + ): + raise ValueError("candidate package metadata differs") + if version != "1.35.1": + raise ValueError("this reviewed successor candidate must be version 1.35.1") + return { + "published": False, + "version": version, + "source_commit": source, + "observed_at": datetime.now(timezone.utc).isoformat(), + "required_external_gates": [ + "Review and merge the exact candidate", + "Run the protected-main full qualification and three-OS lifecycle", + "Publish and verify both immutable artifacts", + "Issue and project the exact signed admission", + ], + "artifacts": [ + { + "kind": kind, + "name": path.name, + "sha256": sha(path.read_bytes()), + "size_bytes": path.stat().st_size, + } + for kind, path in (("python-wheel", wheel), ("python-sdist", sdist)) + ], + } + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--measured-manifest", type=Path) parser.add_argument("--out", type=Path, required=True) + parser.add_argument("--unpublished-wheel", type=Path) + parser.add_argument("--unpublished-sdist", type=Path) + parser.add_argument("--source-commit") args = parser.parse_args() - observation = observe_release() + local_args = (args.unpublished_wheel, args.unpublished_sdist, args.source_commit) + if any(local_args) and not all(local_args): + parser.error( + "unpublished wheel, sdist, and source commit must be supplied together" + ) + observation = ( + observe_unpublished(*local_args) if all(local_args) else observe_release() + ) wheel = next(a for a in observation["artifacts"] if a["kind"] == "python-wheel") ledger = read_json(ROOT / "production-lifecycle-admissions.json") previous = [] @@ -268,7 +485,11 @@ def main() -> int: candidate["measured_scope"] = measured_document.get("scope") candidate["limitations"] = measured_document.get("limitations", []) try: - summary, measured = summarize(args.measured_manifest, wheel["sha256"]) + summary, measured = summarize( + args.measured_manifest, + wheel["sha256"], + version=observation.get("version", VERSION), + ) except (ValueError, trust.TrustError) as exc: candidate["validation_errors"] = [str(exc)] else: @@ -280,7 +501,9 @@ def main() -> int: candidate["validation_errors"] = ["measured scope must be explicit"] else: candidate.update( - state="ready-for-review", + state="ready-for-release-review" + if observation.get("published") is False + else "ready-for-review", campaign_summary=summary, measured_evidence=measured, ) diff --git a/tests/test_measured_flow_candidate.py b/tests/test_measured_flow_candidate.py index 722c0c0..b16d9d6 100644 --- a/tests/test_measured_flow_candidate.py +++ b/tests/test_measured_flow_candidate.py @@ -2,6 +2,9 @@ import importlib.util import json +import io +import tarfile +import zipfile from pathlib import Path import pytest @@ -17,12 +20,53 @@ WHEEL = "sha256:" + "a" * 64 -def manifest(tmp_path): - artifact = tmp_path / "observed.json" - artifact.write_text('{"fixture":true}\n') - digest = candidate.sha(artifact.read_bytes()) - trials = [] +def manifest(tmp_path, *, wheel=None, version="1.35.0"): + # These are explicit parser fixtures, not evidence that a runtime executed. + if wheel is None: + wheel, _ = archives(tmp_path, version, version) + digest = candidate.sha(wheel.read_bytes()) artifacts = [] + + def retain(path): + reference = { + "path": path.name, + "sha256": candidate.sha(path.read_bytes()), + "size_bytes": path.stat().st_size, + } + artifacts.append(reference) + return reference + + wheel_reference = retain(wheel) + with zipfile.ZipFile(wheel) as archive: + members = [ + { + "path": name, + "size_bytes": len(archive.read(name)), + "wheel_sha256": candidate.sha(archive.read(name)), + "installed_sha256": candidate.sha(archive.read(name)), + } + for name in archive.namelist() + if name.startswith("openadapt_flow/") + ] + proof_path = tmp_path / "installed.json" + proof_path.write_text( + json.dumps( + { + "schema_version": "openadapt.installed-distribution-proof/v1", + "distribution": "openadapt-flow", + "version": version, + "wheel": wheel_reference, + "members": members, + "installed_extra_files": [], + "all_members_match": True, + } + ) + ) + proof = retain(proof_path) + normalizer_path = tmp_path / "normalizer.py" + normalizer_path.write_text("# Explicit parser fixture; no execution claim\n") + normalizer = retain(normalizer_path) + trials = [] for name in candidate.trust.CAMPAIGN_CLASSES: counters = dict.fromkeys(candidate.COUNTERS, 0) if name == "uncertain_delivery": @@ -39,33 +83,65 @@ def manifest(tmp_path): ): counters[field] = 1 for trial in range(3): - artifact = tmp_path / f"{name}-{trial}.json" - artifact.write_text( - json.dumps({"fixture": True, "class": name, "trial": trial}) - ) - digest = candidate.sha(artifact.read_bytes()) - artifacts.append( - { - "path": artifact.name, - "sha256": digest, - "size_bytes": artifact.stat().st_size, - } - ) + success = name not in {"safe_halt", "uncertain_delivery"} + report = { + "fixture": True, + "trial_identity": [name, trial], + "success": success, + "transaction_outcome": "VERIFIED" + if success + else "RECONCILIATION_REQUIRED", + "model_calls": 0, + } + report_path = tmp_path / f"{name}-{trial}-report.json" + report_path.write_text(json.dumps(report)) + report_reference = retain(report_path) + observed = { + "schema_version": "openadapt.observed-release-trial/v1", + "class": name, + "task_id": "test-fixture", + "condition": "one", + "trial": trial, + "counters": counters.copy(), + "runtime": { + "version": version, + "wheel_sha256": digest, + "installed_distribution_proof": proof, + }, + "reports": [ + { + **report_reference, + "role": "primary", + **{ + k: report[k] + for k in ("success", "transaction_outcome", "model_calls") + }, + } + ], + "verification_references": [ + {**report_reference, "role": "explicit-parser-fixture"} + ], + } + path = tmp_path / f"{name}-{trial}.json" + path.write_text(json.dumps(observed)) + reference = retain(path) trials.append( { "class": name, "task_id": "test-fixture", "condition": "one", "trial": trial, - "runtime_version": candidate.VERSION, - "artifacts": [{"path": artifact.name, "sha256": digest}], + "runtime_version": version, + "observation": reference, + "artifacts": [reference, report_reference], "counters": counters.copy(), } ) value = { "schema_version": "openadapt.measured-release-evidence/v1", "evidence_class": "remote-safe-synthetic", - "runtime": {"version": candidate.VERSION, "wheel_sha256": WHEEL}, + "runtime": {"version": version, "wheel_sha256": digest}, + "normalizer": normalizer, "artifacts": artifacts, "trials": trials, } @@ -75,8 +151,8 @@ def manifest(tmp_path): def test_counts_derive_from_unique_observed_trials(tmp_path): - path, _ = manifest(tmp_path) - summary, evidence = candidate.summarize(path, WHEEL) + path, value = manifest(tmp_path) + summary, evidence = candidate.summarize(path, value["runtime"]["wheel_sha256"]) assert evidence["trial_count"] == 18 assert evidence["manifest_sha256"] == candidate.sha(path.read_bytes()) assert summary["governed_repair"]["approved_repair_count"] == 3 @@ -96,17 +172,18 @@ def test_counts_derive_from_unique_observed_trials(tmp_path): ) def test_incomplete_or_unsafe_evidence_refuses(tmp_path, mutation): path, value = manifest(tmp_path) + wheel_digest = value["runtime"]["wheel_sha256"] mutation(value) path.write_text(json.dumps(value)) with pytest.raises((ValueError, candidate.trust.TrustError)): - candidate.summarize(path, WHEEL) + candidate.summarize(path, wheel_digest) def test_changed_artifact_bytes_refuse(tmp_path): - path, _ = manifest(tmp_path) + path, value = manifest(tmp_path) (tmp_path / "healthy-0.json").write_text("changed") with pytest.raises(ValueError, match="artifact bytes differ"): - candidate.summarize(path, WHEEL) + candidate.summarize(path, value["runtime"]["wheel_sha256"]) def test_reused_observation_cannot_count_as_another_trial(tmp_path): @@ -114,7 +191,7 @@ def test_reused_observation_cannot_count_as_another_trial(tmp_path): value["trials"][1]["artifacts"] = value["trials"][0]["artifacts"] path.write_text(json.dumps(value)) with pytest.raises(ValueError, match="unique observed evidence"): - candidate.summarize(path, WHEEL) + candidate.summarize(path, value["runtime"]["wheel_sha256"]) def test_incomplete_input_writes_refusal_candidate(tmp_path, monkeypatch): @@ -130,7 +207,11 @@ def test_incomplete_input_writes_refusal_candidate(tmp_path, monkeypatch): monkeypatch.setattr( candidate, "observe_release", - lambda: {"artifacts": [{"kind": "python-wheel", "sha256": WHEEL}]}, + lambda: { + "artifacts": [ + {"kind": "python-wheel", "sha256": value["runtime"]["wheel_sha256"]} + ] + }, ) output = tmp_path / "candidate.json" monkeypatch.setattr( @@ -144,3 +225,220 @@ def test_incomplete_input_writes_refusal_candidate(tmp_path, monkeypatch): assert result["admission_issued"] is False assert "declared_attended, governed_repair" in result["validation_errors"][0] assert result["limitations"] == value["limitations"] + + +def archives(tmp_path, wheel_version="1.35.1", sdist_version="1.35.1"): + wheel = tmp_path / "candidate.whl" + sdist = tmp_path / "candidate.tar.gz" + with zipfile.ZipFile(wheel, "w") as output: + output.writestr( + "openadapt_flow/__init__.py", f'__version__ = "{wheel_version}"\n' + ) + output.writestr( + "openadapt_flow.dist-info/METADATA", + f"Name: openadapt-flow\nVersion: {wheel_version}\n", + ) + metadata = f"Name: openadapt-flow\nVersion: {sdist_version}\n".encode() + with tarfile.open(sdist, "w:gz") as output: + member = tarfile.TarInfo("openadapt_flow/PKG-INFO") + member.size = len(metadata) + output.addfile(member, io.BytesIO(metadata)) + return wheel, sdist + + +@pytest.mark.parametrize("versions", [("1.35.1", "1.35.0"), ("1.35.0", "1.35.0")]) +def test_wrong_unpublished_package_versions_refuse(tmp_path, versions): + wheel, sdist = archives(tmp_path, *versions) + with pytest.raises(ValueError): + candidate.observe_unpublished(wheel, sdist, "c" * 40) + + +def test_unpublished_candidate_cannot_assert_admission_or_publication( + tmp_path, monkeypatch +): + wheel, sdist = archives(tmp_path) + path, value = manifest(tmp_path, wheel=wheel, version="1.35.1") + value.update( + candidate_ready=True, scope="Explicit test fixture; no execution claim" + ) + value["runtime"] = { + "version": "1.35.1", + "wheel_sha256": candidate.sha(wheel.read_bytes()), + } + for trial in value["trials"]: + trial["runtime_version"] = "1.35.1" + path.write_text(json.dumps(value)) + output = tmp_path / "candidate.json" + monkeypatch.setattr( + candidate.sys, + "argv", + [ + "prepare.py", + "--measured-manifest", + str(path), + "--out", + str(output), + "--unpublished-wheel", + str(wheel), + "--unpublished-sdist", + str(sdist), + "--source-commit", + "c" * 40, + ], + ) + assert candidate.main() == 0 + result = json.loads(output.read_text()) + assert result["state"] == "ready-for-release-review" + assert result["admission_issued"] is False + assert result["release_observation"]["published"] is False + assert result["release_observation"]["source_commit"] == "c" * 40 + assert len(result["release_observation"]["required_external_gates"]) == 4 + assert result["measured_evidence"]["trial_count"] == 18 + + +def test_partial_unpublished_inputs_refuse_before_network_observation( + tmp_path, monkeypatch +): + monkeypatch.setattr( + candidate.sys, + "argv", + [ + "prepare.py", + "--out", + str(tmp_path / "out.json"), + "--unpublished-wheel", + str(tmp_path / "candidate.whl"), + ], + ) + monkeypatch.setattr( + candidate, "observe_release", lambda: pytest.fail("unexpected network request") + ) + with pytest.raises(SystemExit) as error: + candidate.main() + assert error.value.code == 2 + + +def update_references(value, path): + """Rehash a deliberately modified fixture without hiding semantic changes.""" + digest = candidate.sha(path.read_bytes()) + + def walk(node): + if isinstance(node, dict): + if node.get("path") == path.name: + node["sha256"] = digest + if "size_bytes" in node: + node["size_bytes"] = path.stat().st_size + for child in node.values(): + walk(child) + elif isinstance(node, list): + for child in node: + walk(child) + + walk(value) + + +def test_rehashed_observation_cannot_relabel_a_failed_trial(tmp_path): + path, value = manifest(tmp_path) + observed_path = tmp_path / "healthy-0.json" + observed = json.loads(observed_path.read_text()) + observed["class"] = "safe_halt" + observed["counters"]["unsafe_effect_count"] = 1 + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with pytest.raises(ValueError, match="retained observation differs"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + +@pytest.mark.parametrize( + "class_name,field", + [ + ("uncertain_delivery", "reconciliation_required_count"), + ("declared_attended", "authenticated_bound_decision_count"), + ("governed_repair", "approved_repair_count"), + ], +) +def test_class_events_cannot_be_concentrated_in_one_trial(tmp_path, class_name, field): + path, value = manifest(tmp_path) + selected = [trial for trial in value["trials"] if trial["class"] == class_name] + for trial, count in zip(selected, [3, 0, 0]): + trial["counters"][field] = count + observed_path = tmp_path / trial["observation"]["path"] + observed = json.loads(observed_path.read_text()) + observed["counters"][field] = count + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with pytest.raises(ValueError, match="each trial must satisfy"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + +def test_attended_decision_completion_cannot_replace_native_verified_effect(tmp_path): + path, value = manifest(tmp_path) + report_path = tmp_path / "declared_attended-0-report.json" + report = json.loads(report_path.read_text()) + report.update( + success=False, transaction_outcome="COMPLETED_UNVERIFIED", report_success=True + ) + report_path.write_text(json.dumps(report)) + update_references(value, report_path) + observed_path = tmp_path / "declared_attended-0.json" + observed = json.loads(observed_path.read_text()) + update_references(observed, report_path) + observed["reports"][0].update( + success=False, transaction_outcome="COMPLETED_UNVERIFIED" + ) + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with pytest.raises(ValueError, match="primary native outcome"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + +def test_rehashed_native_report_must_match_normalized_claim(tmp_path): + path, value = manifest(tmp_path) + report_path = tmp_path / "healthy-0-report.json" + report = json.loads(report_path.read_text()) + report["model_calls"] = 9 + report_path.write_text(json.dumps(report)) + update_references(value, report_path) + observed_path = tmp_path / "healthy-0.json" + observed = json.loads(observed_path.read_text()) + update_references(observed, report_path) + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with pytest.raises( + ValueError, match="native report differs from observed model_calls" + ): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + +def test_installed_proof_cannot_omit_a_wheel_member(tmp_path): + path, value = manifest(tmp_path) + proof_path = tmp_path / "installed.json" + proof = json.loads(proof_path.read_text()) + proof["members"] = [] + proof_path.write_text(json.dumps(proof)) + update_references(value, proof_path) + for trial in value["trials"]: + observed_path = tmp_path / trial["observation"]["path"] + observed = json.loads(observed_path.read_text()) + update_references(observed, proof_path) + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with pytest.raises(ValueError, match="installed member proof does not cover"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + +@pytest.mark.parametrize("reference", [None, {}, {"path": "", "sha256": ""}]) +def test_missing_or_empty_normalizer_reference_refuses(tmp_path, reference): + path, value = manifest(tmp_path) + if reference is None: + value.pop("normalizer") + else: + value["normalizer"] = reference + path.write_text(json.dumps(value)) + with pytest.raises(ValueError, match="measurement normalizer is absent"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) From 84d076ba9678dbe4a928e5fe37c01e84a4a0505f Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 8 Sep 2026 18:17:03 -0400 Subject: [PATCH 5/6] test: use the standard unittest runner for measured candidates Signed-off-by: abrichr --- tests/test_measured_flow_candidate.py | 559 ++++++++++++++------------ 1 file changed, 312 insertions(+), 247 deletions(-) diff --git a/tests/test_measured_flow_candidate.py b/tests/test_measured_flow_candidate.py index b16d9d6..0e7e5b3 100644 --- a/tests/test_measured_flow_candidate.py +++ b/tests/test_measured_flow_candidate.py @@ -6,8 +6,9 @@ import tarfile import zipfile from pathlib import Path - -import pytest +import tempfile +import unittest +from unittest import mock _PATH = ( Path(__file__).resolve().parents[1] @@ -21,7 +22,6 @@ def manifest(tmp_path, *, wheel=None, version="1.35.0"): - # These are explicit parser fixtures, not evidence that a runtime executed. if wheel is None: wheel, _ = archives(tmp_path, version, version) digest = candidate.sha(wheel.read_bytes()) @@ -147,84 +147,7 @@ def retain(path): } path = tmp_path / "manifest.json" path.write_text(json.dumps(value)) - return path, value - - -def test_counts_derive_from_unique_observed_trials(tmp_path): - path, value = manifest(tmp_path) - summary, evidence = candidate.summarize(path, value["runtime"]["wheel_sha256"]) - assert evidence["trial_count"] == 18 - assert evidence["manifest_sha256"] == candidate.sha(path.read_bytes()) - assert summary["governed_repair"]["approved_repair_count"] == 3 - assert summary["healthy"]["minimum_trials_per_cell"] == 3 - - -@pytest.mark.parametrize( - "mutation", - [ - lambda m: m["trials"].pop(), - lambda m: m["trials"].append(m["trials"][0]), - lambda m: m["trials"][0]["counters"].pop("silent_incorrect_success_count"), - lambda m: m["trials"][0]["counters"].update(silent_incorrect_success_count=1), - lambda m: m["runtime"].update(wheel_sha256="sha256:" + "b" * 64), - lambda m: m["trials"][0]["artifacts"].clear(), - ], -) -def test_incomplete_or_unsafe_evidence_refuses(tmp_path, mutation): - path, value = manifest(tmp_path) - wheel_digest = value["runtime"]["wheel_sha256"] - mutation(value) - path.write_text(json.dumps(value)) - with pytest.raises((ValueError, candidate.trust.TrustError)): - candidate.summarize(path, wheel_digest) - - -def test_changed_artifact_bytes_refuse(tmp_path): - path, value = manifest(tmp_path) - (tmp_path / "healthy-0.json").write_text("changed") - with pytest.raises(ValueError, match="artifact bytes differ"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) - - -def test_reused_observation_cannot_count_as_another_trial(tmp_path): - path, value = manifest(tmp_path) - value["trials"][1]["artifacts"] = value["trials"][0]["artifacts"] - path.write_text(json.dumps(value)) - with pytest.raises(ValueError, match="unique observed evidence"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) - - -def test_incomplete_input_writes_refusal_candidate(tmp_path, monkeypatch): - path, value = manifest(tmp_path) - value["scope"] = "Explicit test fixture; no execution claim" - value["limitations"] = ["Two classes were not measured"] - value["trials"] = [ - t - for t in value["trials"] - if t["class"] not in {"declared_attended", "governed_repair"} - ] - path.write_text(json.dumps(value)) - monkeypatch.setattr( - candidate, - "observe_release", - lambda: { - "artifacts": [ - {"kind": "python-wheel", "sha256": value["runtime"]["wheel_sha256"]} - ] - }, - ) - output = tmp_path / "candidate.json" - monkeypatch.setattr( - candidate.sys, - "argv", - ["prepare.py", "--measured-manifest", str(path), "--out", str(output)], - ) - assert candidate.main() == 1 - result = json.loads(output.read_text()) - assert result["state"] == "evidence-incomplete" - assert result["admission_issued"] is False - assert "declared_attended, governed_repair" in result["validation_errors"][0] - assert result["limitations"] == value["limitations"] + return (path, value) def archives(tmp_path, wheel_version="1.35.1", sdist_version="1.35.1"): @@ -243,79 +166,7 @@ def archives(tmp_path, wheel_version="1.35.1", sdist_version="1.35.1"): member = tarfile.TarInfo("openadapt_flow/PKG-INFO") member.size = len(metadata) output.addfile(member, io.BytesIO(metadata)) - return wheel, sdist - - -@pytest.mark.parametrize("versions", [("1.35.1", "1.35.0"), ("1.35.0", "1.35.0")]) -def test_wrong_unpublished_package_versions_refuse(tmp_path, versions): - wheel, sdist = archives(tmp_path, *versions) - with pytest.raises(ValueError): - candidate.observe_unpublished(wheel, sdist, "c" * 40) - - -def test_unpublished_candidate_cannot_assert_admission_or_publication( - tmp_path, monkeypatch -): - wheel, sdist = archives(tmp_path) - path, value = manifest(tmp_path, wheel=wheel, version="1.35.1") - value.update( - candidate_ready=True, scope="Explicit test fixture; no execution claim" - ) - value["runtime"] = { - "version": "1.35.1", - "wheel_sha256": candidate.sha(wheel.read_bytes()), - } - for trial in value["trials"]: - trial["runtime_version"] = "1.35.1" - path.write_text(json.dumps(value)) - output = tmp_path / "candidate.json" - monkeypatch.setattr( - candidate.sys, - "argv", - [ - "prepare.py", - "--measured-manifest", - str(path), - "--out", - str(output), - "--unpublished-wheel", - str(wheel), - "--unpublished-sdist", - str(sdist), - "--source-commit", - "c" * 40, - ], - ) - assert candidate.main() == 0 - result = json.loads(output.read_text()) - assert result["state"] == "ready-for-release-review" - assert result["admission_issued"] is False - assert result["release_observation"]["published"] is False - assert result["release_observation"]["source_commit"] == "c" * 40 - assert len(result["release_observation"]["required_external_gates"]) == 4 - assert result["measured_evidence"]["trial_count"] == 18 - - -def test_partial_unpublished_inputs_refuse_before_network_observation( - tmp_path, monkeypatch -): - monkeypatch.setattr( - candidate.sys, - "argv", - [ - "prepare.py", - "--out", - str(tmp_path / "out.json"), - "--unpublished-wheel", - str(tmp_path / "candidate.whl"), - ], - ) - monkeypatch.setattr( - candidate, "observe_release", lambda: pytest.fail("unexpected network request") - ) - with pytest.raises(SystemExit) as error: - candidate.main() - assert error.value.code == 2 + return (wheel, sdist) def update_references(value, path): @@ -337,108 +188,322 @@ def walk(node): walk(value) -def test_rehashed_observation_cannot_relabel_a_failed_trial(tmp_path): - path, value = manifest(tmp_path) - observed_path = tmp_path / "healthy-0.json" - observed = json.loads(observed_path.read_text()) - observed["class"] = "safe_halt" - observed["counters"]["unsafe_effect_count"] = 1 - observed_path.write_text(json.dumps(observed)) - update_references(value, observed_path) - path.write_text(json.dumps(value)) - with pytest.raises(ValueError, match="retained observation differs"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) +class MeasuredFlowCandidateTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.tmp_path = Path(temporary.name) + + def test_counts_derive_from_unique_observed_trials(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + summary, evidence = candidate.summarize(path, value["runtime"]["wheel_sha256"]) + assert evidence["trial_count"] == 18 + assert evidence["manifest_sha256"] == candidate.sha(path.read_bytes()) + assert summary["governed_repair"]["approved_repair_count"] == 3 + assert summary["healthy"]["minimum_trials_per_cell"] == 3 + + def _check_incomplete_or_unsafe_evidence_refuses(self, mutation): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + wheel_digest = value["runtime"]["wheel_sha256"] + mutation(value) + path.write_text(json.dumps(value)) + with self.assertRaises((ValueError, candidate.trust.TrustError)): + candidate.summarize(path, wheel_digest) + + def test_incomplete_or_unsafe_evidence_refuses_0(self): + self._check_incomplete_or_unsafe_evidence_refuses(lambda m: m["trials"].pop()) + + def test_incomplete_or_unsafe_evidence_refuses_1(self): + self._check_incomplete_or_unsafe_evidence_refuses( + lambda m: m["trials"].append(m["trials"][0]) + ) + def test_incomplete_or_unsafe_evidence_refuses_2(self): + self._check_incomplete_or_unsafe_evidence_refuses( + lambda m: m["trials"][0]["counters"].pop("silent_incorrect_success_count") + ) -@pytest.mark.parametrize( - "class_name,field", - [ - ("uncertain_delivery", "reconciliation_required_count"), - ("declared_attended", "authenticated_bound_decision_count"), - ("governed_repair", "approved_repair_count"), - ], -) -def test_class_events_cannot_be_concentrated_in_one_trial(tmp_path, class_name, field): - path, value = manifest(tmp_path) - selected = [trial for trial in value["trials"] if trial["class"] == class_name] - for trial, count in zip(selected, [3, 0, 0]): - trial["counters"][field] = count - observed_path = tmp_path / trial["observation"]["path"] + def test_incomplete_or_unsafe_evidence_refuses_3(self): + self._check_incomplete_or_unsafe_evidence_refuses( + lambda m: m["trials"][0]["counters"].update( + silent_incorrect_success_count=1 + ) + ) + + def test_incomplete_or_unsafe_evidence_refuses_4(self): + self._check_incomplete_or_unsafe_evidence_refuses( + lambda m: m["runtime"].update(wheel_sha256="sha256:" + "b" * 64) + ) + + def test_incomplete_or_unsafe_evidence_refuses_5(self): + self._check_incomplete_or_unsafe_evidence_refuses( + lambda m: m["trials"][0]["artifacts"].clear() + ) + + def test_changed_artifact_bytes_refuse(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + (tmp_path / "healthy-0.json").write_text("changed") + with self.assertRaisesRegex(ValueError, "artifact bytes differ"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def test_reused_observation_cannot_count_as_another_trial(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + value["trials"][1]["artifacts"] = value["trials"][0]["artifacts"] + path.write_text(json.dumps(value)) + with self.assertRaisesRegex(ValueError, "unique observed evidence"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def test_incomplete_input_writes_refusal_candidate(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + value["scope"] = "Explicit test fixture; no execution claim" + value["limitations"] = ["Two classes were not measured"] + value["trials"] = [ + t + for t in value["trials"] + if t["class"] not in {"declared_attended", "governed_repair"} + ] + path.write_text(json.dumps(value)) + self.enterContext( + mock.patch.object( + candidate, + "observe_release", + lambda: { + "artifacts": [ + { + "kind": "python-wheel", + "sha256": value["runtime"]["wheel_sha256"], + } + ] + }, + ) + ) + output = tmp_path / "candidate.json" + self.enterContext( + mock.patch.object( + candidate.sys, + "argv", + ["prepare.py", "--measured-manifest", str(path), "--out", str(output)], + ) + ) + assert candidate.main() == 1 + result = json.loads(output.read_text()) + assert result["state"] == "evidence-incomplete" + assert result["admission_issued"] is False + assert "declared_attended, governed_repair" in result["validation_errors"][0] + assert result["limitations"] == value["limitations"] + + def _check_wrong_unpublished_package_versions_refuse(self, versions): + tmp_path = self.tmp_path + wheel, sdist = archives(tmp_path, *versions) + with self.assertRaises(ValueError): + candidate.observe_unpublished(wheel, sdist, "c" * 40) + + def test_wrong_unpublished_package_versions_refuse_0(self): + self._check_wrong_unpublished_package_versions_refuse(("1.35.1", "1.35.0")) + + def test_wrong_unpublished_package_versions_refuse_1(self): + self._check_wrong_unpublished_package_versions_refuse(("1.35.0", "1.35.0")) + + def test_unpublished_candidate_cannot_assert_admission_or_publication(self): + tmp_path = self.tmp_path + wheel, sdist = archives(tmp_path) + path, value = manifest(tmp_path, wheel=wheel, version="1.35.1") + value.update( + candidate_ready=True, scope="Explicit test fixture; no execution claim" + ) + value["runtime"] = { + "version": "1.35.1", + "wheel_sha256": candidate.sha(wheel.read_bytes()), + } + for trial in value["trials"]: + trial["runtime_version"] = "1.35.1" + path.write_text(json.dumps(value)) + output = tmp_path / "candidate.json" + self.enterContext( + mock.patch.object( + candidate.sys, + "argv", + [ + "prepare.py", + "--measured-manifest", + str(path), + "--out", + str(output), + "--unpublished-wheel", + str(wheel), + "--unpublished-sdist", + str(sdist), + "--source-commit", + "c" * 40, + ], + ) + ) + assert candidate.main() == 0 + result = json.loads(output.read_text()) + assert result["state"] == "ready-for-release-review" + assert result["admission_issued"] is False + assert result["release_observation"]["published"] is False + assert result["release_observation"]["source_commit"] == "c" * 40 + assert len(result["release_observation"]["required_external_gates"]) == 4 + assert result["measured_evidence"]["trial_count"] == 18 + + def test_partial_unpublished_inputs_refuse_before_network_observation(self): + tmp_path = self.tmp_path + self.enterContext( + mock.patch.object( + candidate.sys, + "argv", + [ + "prepare.py", + "--out", + str(tmp_path / "out.json"), + "--unpublished-wheel", + str(tmp_path / "candidate.whl"), + ], + ) + ) + self.enterContext( + mock.patch.object( + candidate, + "observe_release", + lambda: self.fail("unexpected network request"), + ) + ) + with self.assertRaises(SystemExit) as error: + candidate.main() + assert error.exception.code == 2 + + def test_rehashed_observation_cannot_relabel_a_failed_trial(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + observed_path = tmp_path / "healthy-0.json" observed = json.loads(observed_path.read_text()) - observed["counters"][field] = count + observed["class"] = "safe_halt" + observed["counters"]["unsafe_effect_count"] = 1 observed_path.write_text(json.dumps(observed)) update_references(value, observed_path) - path.write_text(json.dumps(value)) - with pytest.raises(ValueError, match="each trial must satisfy"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) - + path.write_text(json.dumps(value)) + with self.assertRaisesRegex(ValueError, "retained observation differs"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) -def test_attended_decision_completion_cannot_replace_native_verified_effect(tmp_path): - path, value = manifest(tmp_path) - report_path = tmp_path / "declared_attended-0-report.json" - report = json.loads(report_path.read_text()) - report.update( - success=False, transaction_outcome="COMPLETED_UNVERIFIED", report_success=True - ) - report_path.write_text(json.dumps(report)) - update_references(value, report_path) - observed_path = tmp_path / "declared_attended-0.json" - observed = json.loads(observed_path.read_text()) - update_references(observed, report_path) - observed["reports"][0].update( - success=False, transaction_outcome="COMPLETED_UNVERIFIED" - ) - observed_path.write_text(json.dumps(observed)) - update_references(value, observed_path) - path.write_text(json.dumps(value)) - with pytest.raises(ValueError, match="primary native outcome"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) - - -def test_rehashed_native_report_must_match_normalized_claim(tmp_path): - path, value = manifest(tmp_path) - report_path = tmp_path / "healthy-0-report.json" - report = json.loads(report_path.read_text()) - report["model_calls"] = 9 - report_path.write_text(json.dumps(report)) - update_references(value, report_path) - observed_path = tmp_path / "healthy-0.json" - observed = json.loads(observed_path.read_text()) - update_references(observed, report_path) - observed_path.write_text(json.dumps(observed)) - update_references(value, observed_path) - path.write_text(json.dumps(value)) - with pytest.raises( - ValueError, match="native report differs from observed model_calls" + def _check_class_events_cannot_be_concentrated_in_one_trial( + self, class_name, field ): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + selected = [trial for trial in value["trials"] if trial["class"] == class_name] + for trial, count in zip(selected, [3, 0, 0]): + trial["counters"][field] = count + observed_path = tmp_path / trial["observation"]["path"] + observed = json.loads(observed_path.read_text()) + observed["counters"][field] = count + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with self.assertRaisesRegex(ValueError, "each trial must satisfy"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def test_class_events_cannot_be_concentrated_in_one_trial_0(self): + self._check_class_events_cannot_be_concentrated_in_one_trial( + "uncertain_delivery", "reconciliation_required_count" + ) + def test_class_events_cannot_be_concentrated_in_one_trial_1(self): + self._check_class_events_cannot_be_concentrated_in_one_trial( + "declared_attended", "authenticated_bound_decision_count" + ) -def test_installed_proof_cannot_omit_a_wheel_member(tmp_path): - path, value = manifest(tmp_path) - proof_path = tmp_path / "installed.json" - proof = json.loads(proof_path.read_text()) - proof["members"] = [] - proof_path.write_text(json.dumps(proof)) - update_references(value, proof_path) - for trial in value["trials"]: - observed_path = tmp_path / trial["observation"]["path"] + def test_class_events_cannot_be_concentrated_in_one_trial_2(self): + self._check_class_events_cannot_be_concentrated_in_one_trial( + "governed_repair", "approved_repair_count" + ) + + def test_attended_decision_completion_cannot_replace_native_verified_effect(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + report_path = tmp_path / "declared_attended-0-report.json" + report = json.loads(report_path.read_text()) + report.update( + success=False, + transaction_outcome="COMPLETED_UNVERIFIED", + report_success=True, + ) + report_path.write_text(json.dumps(report)) + update_references(value, report_path) + observed_path = tmp_path / "declared_attended-0.json" observed = json.loads(observed_path.read_text()) - update_references(observed, proof_path) + update_references(observed, report_path) + observed["reports"][0].update( + success=False, transaction_outcome="COMPLETED_UNVERIFIED" + ) observed_path.write_text(json.dumps(observed)) update_references(value, observed_path) - path.write_text(json.dumps(value)) - with pytest.raises(ValueError, match="installed member proof does not cover"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) - - -@pytest.mark.parametrize("reference", [None, {}, {"path": "", "sha256": ""}]) -def test_missing_or_empty_normalizer_reference_refuses(tmp_path, reference): - path, value = manifest(tmp_path) - if reference is None: - value.pop("normalizer") - else: - value["normalizer"] = reference - path.write_text(json.dumps(value)) - with pytest.raises(ValueError, match="measurement normalizer is absent"): - candidate.summarize(path, value["runtime"]["wheel_sha256"]) + path.write_text(json.dumps(value)) + with self.assertRaisesRegex(ValueError, "primary native outcome"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def test_rehashed_native_report_must_match_normalized_claim(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + report_path = tmp_path / "healthy-0-report.json" + report = json.loads(report_path.read_text()) + report["model_calls"] = 9 + report_path.write_text(json.dumps(report)) + update_references(value, report_path) + observed_path = tmp_path / "healthy-0.json" + observed = json.loads(observed_path.read_text()) + update_references(observed, report_path) + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with self.assertRaisesRegex( + ValueError, "native report differs from observed model_calls" + ): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def test_installed_proof_cannot_omit_a_wheel_member(self): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + proof_path = tmp_path / "installed.json" + proof = json.loads(proof_path.read_text()) + proof["members"] = [] + proof_path.write_text(json.dumps(proof)) + update_references(value, proof_path) + for trial in value["trials"]: + observed_path = tmp_path / trial["observation"]["path"] + observed = json.loads(observed_path.read_text()) + update_references(observed, proof_path) + observed_path.write_text(json.dumps(observed)) + update_references(value, observed_path) + path.write_text(json.dumps(value)) + with self.assertRaisesRegex( + ValueError, "installed member proof does not cover" + ): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def _check_missing_or_empty_normalizer_reference_refuses(self, reference): + tmp_path = self.tmp_path + path, value = manifest(tmp_path) + if reference is None: + value.pop("normalizer") + else: + value["normalizer"] = reference + path.write_text(json.dumps(value)) + with self.assertRaisesRegex(ValueError, "measurement normalizer is absent"): + candidate.summarize(path, value["runtime"]["wheel_sha256"]) + + def test_missing_or_empty_normalizer_reference_refuses_0(self): + self._check_missing_or_empty_normalizer_reference_refuses(None) + + def test_missing_or_empty_normalizer_reference_refuses_1(self): + self._check_missing_or_empty_normalizer_reference_refuses({}) + + def test_missing_or_empty_normalizer_reference_refuses_2(self): + self._check_missing_or_empty_normalizer_reference_refuses( + {"path": "", "sha256": ""} + ) From 51547873ea778ec6c67917d527399acc3be7079f Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 8 Sep 2026 18:35:52 -0400 Subject: [PATCH 6/6] docs: retain the measured unsigned Flow 1.35.1 candidate Signed-off-by: abrichr --- .../flow-1.35.1-measured/README.md | 41 ++++ .../flow-1.35.1-measured/candidate.json | 203 ++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 local-candidates/flow-1.35.1-measured/README.md create mode 100644 local-candidates/flow-1.35.1-measured/candidate.json diff --git a/local-candidates/flow-1.35.1-measured/README.md b/local-candidates/flow-1.35.1-measured/README.md new file mode 100644 index 0000000..870afcb --- /dev/null +++ b/local-candidates/flow-1.35.1-measured/README.md @@ -0,0 +1,41 @@ +# Flow 1.35.1 measured release candidate + +This unsigned candidate passes the six-class evidence checks for the exact +local wheel. It is ready for release review. Publication and admission remain +false. + +The source commit is `aed32758b7342c61787a3b56fe940e1e9f2d648a`. The retained +manifest digest is +`sha256:2cd3b47a9c1fc3d81b1207e7f64441d82ad7f0f62babf49d8ffde6ae2ac2c7ca`. +The verifier checks all 1,617 inventoried artifacts and 36 selected trial groups. + +| Class | Selected groups | Required outcome | +| --- | ---: | --- | +| Healthy | 6 | Verified effect | +| Safe halt | 15 | Refusal before an unsafe effect | +| Uncertain delivery | 6 | Reconciliation required, without retry or replay | +| Idempotency replay | 3 | One verified write, then same-key refusal with unchanged input and effect records | +| Declared attended | 3 | Bound synthetic decision, live revalidation, and verified final effect | +| Governed repair | 3 | Reviewed and approved candidate, complete campaigns, retained proof, and verified canary | + +Each of the 12 selected task-condition cells has three groups. The 42 complete +raw groups retain 54 native phase reports. The six excluded groups are the three +moderate-display trials, whose declared contract permits a verified effect or a +safe halt, and three retained-write timeout trials, whose complete effect proof +permits `VERIFIED`. The selected uncertain-delivery class requires +`RECONCILIATION_REQUIRED`. Two incomplete repair instrumentation/restart attempts +remain retained and excluded. Classes are distinct, but outcome indicators can +overlap across phases; these counts don't define an overall success percentage. + +The scope is synthetic pixels, local SQLite, and synthetic reviewer inputs. +The actual repair refreshes the typed field's OCR anchor at step 002. The +hidden-Save halt is a separate refusal check. No customer qualification or +actual human approval is established. Compact scaled identity regions and the +excluded moderate-display identity-tokenization case can still refuse. Their +exact evidence remains in the manifest; no threshold was relaxed. + +The source, wheel, sdist, and measured evidence require review before the +protected-main qualification and publication steps. A changed published wheel +requires new exact-byte evidence. The existing issuer must then bind the +reviewed release and workflow contracts before any signed admission or live +projection changes. The published Flow admission still names 1.34.0. diff --git a/local-candidates/flow-1.35.1-measured/candidate.json b/local-candidates/flow-1.35.1-measured/candidate.json new file mode 100644 index 0000000..8e2261f --- /dev/null +++ b/local-candidates/flow-1.35.1-measured/candidate.json @@ -0,0 +1,203 @@ +{ + "admission_issued": false, + "campaign_summary": { + "declared_attended": { + "approved_repair_count": 0, + "authenticated_bound_decision_count": 3, + "blind_retry_count": 0, + "live_target_revalidation_count": 3, + "minimum_trials_per_cell": 3, + "model_call_count": 0, + "observed_trial_count": 3, + "over_halt_count": 0, + "policy_approved_repair_count": 0, + "reconciliation_required_count": 0, + "replay_dispatch_count": 0, + "retained_repair_evidence_count": 0, + "silent_incorrect_success_count": 0, + "task_condition_cell_count": 1, + "unplanned_intervention_count": 0, + "unsafe_effect_count": 0, + "unverified_direct_action_count": 0 + }, + "governed_repair": { + "approved_repair_count": 3, + "authenticated_bound_decision_count": 0, + "blind_retry_count": 0, + "live_target_revalidation_count": 3, + "minimum_trials_per_cell": 3, + "model_call_count": 0, + "observed_trial_count": 3, + "over_halt_count": 0, + "policy_approved_repair_count": 3, + "reconciliation_required_count": 3, + "replay_dispatch_count": 0, + "retained_repair_evidence_count": 3, + "silent_incorrect_success_count": 0, + "task_condition_cell_count": 1, + "unplanned_intervention_count": 0, + "unsafe_effect_count": 0, + "unverified_direct_action_count": 0 + }, + "healthy": { + "approved_repair_count": 0, + "authenticated_bound_decision_count": 0, + "blind_retry_count": 0, + "live_target_revalidation_count": 0, + "minimum_trials_per_cell": 3, + "model_call_count": 0, + "observed_trial_count": 6, + "over_halt_count": 0, + "policy_approved_repair_count": 0, + "reconciliation_required_count": 0, + "replay_dispatch_count": 0, + "retained_repair_evidence_count": 0, + "silent_incorrect_success_count": 0, + "task_condition_cell_count": 2, + "unplanned_intervention_count": 0, + "unsafe_effect_count": 0, + "unverified_direct_action_count": 0 + }, + "idempotency_replay": { + "approved_repair_count": 0, + "authenticated_bound_decision_count": 0, + "blind_retry_count": 0, + "live_target_revalidation_count": 0, + "minimum_trials_per_cell": 3, + "model_call_count": 0, + "observed_trial_count": 3, + "over_halt_count": 0, + "policy_approved_repair_count": 0, + "reconciliation_required_count": 0, + "replay_dispatch_count": 0, + "retained_repair_evidence_count": 0, + "silent_incorrect_success_count": 0, + "task_condition_cell_count": 1, + "unplanned_intervention_count": 0, + "unsafe_effect_count": 0, + "unverified_direct_action_count": 0 + }, + "safe_halt": { + "approved_repair_count": 0, + "authenticated_bound_decision_count": 0, + "blind_retry_count": 0, + "live_target_revalidation_count": 0, + "minimum_trials_per_cell": 3, + "model_call_count": 0, + "observed_trial_count": 15, + "over_halt_count": 0, + "policy_approved_repair_count": 0, + "reconciliation_required_count": 12, + "replay_dispatch_count": 0, + "retained_repair_evidence_count": 0, + "silent_incorrect_success_count": 0, + "task_condition_cell_count": 5, + "unplanned_intervention_count": 0, + "unsafe_effect_count": 0, + "unverified_direct_action_count": 0 + }, + "uncertain_delivery": { + "approved_repair_count": 0, + "authenticated_bound_decision_count": 0, + "blind_retry_count": 0, + "live_target_revalidation_count": 0, + "minimum_trials_per_cell": 3, + "model_call_count": 0, + "observed_trial_count": 6, + "over_halt_count": 0, + "policy_approved_repair_count": 0, + "reconciliation_required_count": 6, + "replay_dispatch_count": 0, + "retained_repair_evidence_count": 0, + "silent_incorrect_success_count": 0, + "task_condition_cell_count": 2, + "unplanned_intervention_count": 0, + "unsafe_effect_count": 0, + "unverified_direct_action_count": 0 + } + }, + "canonical_policy_commit": "9b61391713203998e584f4834e4812bb41b40f6e", + "evidence_class": "remote-safe-synthetic", + "limitations": [ + { + "id": "compact-scale-safe-refusal", + "observed": "The actual runtime refuses unreadable identity at this compact scale. The corrected campaign must match that refusal. This candidate does not establish broad scale support.", + "scope": "Compact synthetic identifier at 1.15x display scale", + "verification_artifacts": [ + "source-fix-repros/repair-independent-review/sampler-runtime-parity.json", + "source-fix-repros/repair-independent-review/sampler_runtime_parity.py", + "source-fix-repros/repair-independent-review/retention-provenance.json" + ] + }, + { + "follow_up": "Investigate identity tokenization sensitivity to a one-pixel resolved target shift while preserving exact identity and ambiguity refusal requirements.", + "id": "moderate-display-identity-tokenization-refusal", + "interpretation": "The reports establish one-pixel resolution/identity-tokenization sensitivity after the OCR enumeration change. This is not a target miss. No counterfactual execution or tuning experiment was run, so this record does not isolate every internal causal contribution.", + "observed": "All three old trials verify. All three candidate trials refuse identity before Save. Both releases find Save at OCR confidence 1.0 in identical before frames. The resolved x coordinate changes from 1060 to 1061. The candidate identity OCR joins Cardiology and selected, and coverage changes from 1.0 to 0.2381.", + "qualification_accounting": "The predeclared condition permits VERIFIED or a safe halt. All three candidate trials meet that alternative but remain excluded from the six-class selected trial count. No overall parity or improvement claim follows.", + "scope": "The three excluded moderate_display_drift trials compared with frozen 1.35.0", + "verification_artifacts": [ + "limitations/moderate-display-identity-tokenization/comparison.json" + ] + } + ], + "measured_evidence": { + "artifact_count": 1617, + "manifest_sha256": "sha256:2cd3b47a9c1fc3d81b1207e7f64441d82ad7f0f62babf49d8ffde6ae2ac2c7ca", + "task_count": 3, + "trial_count": 36 + }, + "measured_manifest_sha256": "sha256:2cd3b47a9c1fc3d81b1207e7f64441d82ad7f0f62babf49d8ffde6ae2ac2c7ca", + "measured_scope": { + "application": "Pillow synthetic clinic fixture", + "operator_principal": "Explicit synthetic local protocol inputs; no real human/authentication provider proof", + "promoted_repair": "Retained typed-field OCR anchor refresh at step_002. The hidden-Save halt is a separate expected halt check; the promoted anchor does not fix or unhide Save. Discovery and canary use distinct reset SQLite oracles.", + "required_path": "Default real attended and encrypted repair paths; added deployment adapters/plaintext diagnostics do not substitute for those paths", + "runtime": "Exact installed unmodified candidate wheel; identity binds the version and digest", + "system_of_record": "Local SQLite" + }, + "proposed_release_identity": { + "channel": "production", + "previous_admission_sha256": "sha256:d2983b86d5428c892421b191f6943a63cf4f9a0b1629e324d099f5df6fa5dcda", + "schema_version": "openadapt.monotonic-production-release/v1", + "sequence": 2 + }, + "release_observation": { + "artifacts": [ + { + "kind": "python-wheel", + "name": "openadapt_flow-1.35.1-py3-none-any.whl", + "sha256": "sha256:3e0d3c0fc423b6055d1972a2c8f90d9a5622330a0888f2cfaaeb7993ff23cb99", + "size_bytes": 2129387 + }, + { + "kind": "python-sdist", + "name": "openadapt_flow-1.35.1.tar.gz", + "sha256": "sha256:e84ccd8e7dd32ad074d5becb351d806e7a735fa1ef5e6ae95c9d4bb48f621588", + "size_bytes": 21133468 + } + ], + "observed_at": "2026-09-08T22:35:52.508027+00:00", + "published": false, + "required_external_gates": [ + "Review and merge the exact candidate", + "Run the protected-main full qualification and three-OS lifecycle", + "Publish and verify both immutable artifacts", + "Issue and project the exact signed admission" + ], + "source_commit": "aed32758b7342c61787a3b56fe940e1e9f2d648a", + "version": "1.35.1" + }, + "required_campaign_classes": [ + "healthy", + "safe_halt", + "idempotency_replay", + "uncertain_delivery", + "declared_attended", + "governed_repair" + ], + "schema_version": "openadapt.unsigned-measured-release-candidate/v1", + "scope": "Executed local synthetic SQLite and pixel fixture; synthetic reviewer inputs; no customer qualification or actual human approval.", + "state": "ready-for-release-review", + "target": "flow" +}