Skip to content

feat(eval): define ADR-0008 x64_freeze.resource_manifest_sha256 digest contract - #26

Merged
srinivasdrona merged 8 commits into
mainfrom
fix/clinvar-x64-manifest-contract
Sep 1, 2026
Merged

srinivasdrona merged 8 commits into
mainfrom
fix/clinvar-x64-manifest-contract

Conversation

@srinivasdrona

Copy link
Copy Markdown
Owner

Summary

Defines exactly and reproducibly what bytes x64_freeze.resource_manifest_sha256 hashes. Previously assert_runtime_boundary (src/raptor/eval/prospective_freeze.py) accepted any 64-character lowercase hex string for this field with no definition of what it must be computed from — the pin was unreproducible.

What this adds

  • raptor.eval.prospective_freeze.compute_resource_manifest_sha256 / resource_manifest_entries: a versioned, domain-separated canonical-JSON envelope (schema: raptor.eval.adr0008_resource_manifest_digest.v1) binding, in a fixed pinned order, the identity, order, and raw-byte SHA-256 of the three pinned checksum-manifest files already named in configs/eval/core_annotation_bundle.yaml (x64_handoff_requirements.items) and required by docs/ops/masked-heldout-bias-rerun-handoff.md §4/§9:
    • nirvana-grch38-full.sha256.txt
    • nirvana-grch38-updates.sha256.txt
    • bias-hg38-data.sha256.txt
  • All bytes are read via Path.read_bytes() (binary mode, no text-mode newline translation) — the digest is identical on Windows and Linux for byte-identical files, and changes on any filename/order/content drift.
  • docs/ops/adr-0008-resource-manifest-digest.md: the full human-readable algorithm spec, rationale, and operator usage.
  • scripts/compute_adr0008_resource_manifest_sha256.py: a read-only operator CLI for the ADR-0008-designated x64 worker. Reads only the three manifest files, never touches the multi-GB annotation-data bundles, never contacts a network, never runs BIAS/Nirvana, never writes/mutates anything. Refuses to run on a non-x86_64 host unless --allow-non-x64-host is passed (used only by this repo's own tests).
  • tests/eval/test_adr0008_resource_manifest_digest.py: 16 unit tests covering determinism, content/identity/order sensitivity, fail-closed missing-file handling, a golden-value regression lock on the exact canonical serialization, and CLI integration (including the real refusal path, exercised naturally since this dev host is aarch64).
  • A one-line clarifying edit to the draft approval's resource_manifest_sha256 placeholder comment (still unfilled, still not APPROVED_PRE_DATA) pointing at the new spec/script.

What this intentionally does NOT do

  • Does not change assert_runtime_boundary's behavior — it still only validates the field's format (64-hex-lowercase). No concrete expected value can be pinned in code yet, since it can only be produced by running the new script on the real x64 worker.
  • Does not fill in a real digest value anywhere, and does not change the draft approval's decision away from its non-APPROVED_PRE_DATA placeholder value.
  • Does not contact ClinVar, run BIAS/Nirvana, or touch any external resource file.

Validation

All commands run in WSL2 Ubuntu, /home/sdrona/raptor/bin/python, Python 3.12.3, PYTHONPATH=src:

python -c "import sys; print(sys.executable); print(sys.version)"
# /home/sdrona/raptor/bin/python
# 3.12.3 (main, Jun 19 2026, 12:46:00) [GCC 13.3.0]

python -m pytest tests/eval/test_adr0008_resource_manifest_digest.py -q
# 16 passed

python -m pytest tests/eval/test_clinvar_2026_08_prospective_freeze_red.py \
  tests/eval/test_clinvar_2026_08_prospective_adjudication_red.py \
  tests/eval/test_tiered_prospective_registration.py \
  tests/eval/test_adr0008_resource_manifest_digest.py -q
# 124 passed, 3 skipped

A pre-existing, unrelated 5-test failure in tests/eval/test_tiered_gate_preservation.py / tests/eval/test_tiered_readjudication_frozen_r2.py was confirmed present before this change too (via git stash), so it is out of scope here.

Do not merge — draft, pending real ADR-0008 x64-worker verification of the checksum-manifest files and a human approval decision.

srinivasdrona and others added 8 commits September 1, 2026 10:14
…t contract

Previously `assert_runtime_boundary` accepted any 64-hex-lowercase string
for `x64_freeze.resource_manifest_sha256` with no definition of what bytes
it hashes, making the pin unreproducible.

Add `raptor.eval.prospective_freeze.compute_resource_manifest_sha256` /
`resource_manifest_entries`: a versioned, domain-separated canonical-JSON
envelope binding the identity, pinned order, and raw-byte SHA-256 of the
three pinned checksum-manifest files (nirvana-grch38-full.sha256.txt,
nirvana-grch38-updates.sha256.txt, bias-hg38-data.sha256.txt) already named
in configs/eval/core_annotation_bundle.yaml and docs/ops/masked-heldout-
bias-rerun-handoff.md. Raw-byte reads make the digest identical on Windows
and Linux for byte-identical files, and it changes on any filename/order/
content drift.

Add a read-only operator CLI (scripts/compute_adr0008_resource_manifest_sha256.py)
for the ADR-0008 x64 worker, a full spec/ops doc
(docs/ops/adr-0008-resource-manifest-digest.md), and unit tests. No
network access, no BIAS/Nirvana invocation, no mutation of external
resource files. assert_runtime_boundary's format-only validation and the
existing pre_data_approval schema are unchanged; the draft approval's
resource_manifest_sha256 placeholder is still unfilled and still not
APPROVED_PRE_DATA.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c146921-f3dd-4a1e-8cf0-8f574de49204
…a gate

Governance correction: pre_data_approval (ADR-0020 stages 1-2, ClinVar
transport freeze + raw archive acquisition) incorrectly required and
validated an x64_freeze block, converting any ADR-0008 runtime-identity
drift into a PRE_DATA_DRIFT stop state before any archive GET. ClinVar
archive acquisition has no x86-only requirement.

- validate_pre_data_approval / execute_transport_and_raw_freeze no longer
  accept, require, or check x64_freeze / runtime_identity in any form.
- New validate_scoring_stage_approval (schema raptor.eval.scoring_stage_approval.v1)
  is the separate, later, mandatory gate before any BIAS/Nirvana execution
  or label-dependent evaluation (ADR-0020 stage 4), reusing the existing
  assert_runtime_boundary / compute_resource_manifest_sha256 contract.
  All its failures raise ProspectiveInvalidStateError (INVALID / A0
  run-integrity), never a PRE_DATA_* stop state, matching the registration
  spec's own stage-4 rule.
- Registration spec YAML gains a scoring_stage_approval section and splits
  freeze_record_must_pin accordingly; ops doc and operator-script docstrings
  updated to reflect the new boundary.
- pre_data_approval draft approval has its x64_freeze block removed and
  decision reworded (still not APPROVED_PRE_DATA); a new, separate,
  unapproved scoring_stage_approval draft file is added with the same
  placeholder-only resource_manifest_sha256 (no fabricated digest).
- Tests updated/added accordingly; no ClinVar archive access performed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c146921-f3dd-4a1e-8cf0-8f574de49204
Address 4 independent-review findings against the ADR-0008 scoring-stage
gate introduced in b40dd08:

1. validate_scoring_stage_approval no longer trusts the approval record's
   claimed x64_freeze block. It now requires an explicit APPROVED_SCORING_STAGE
   decision (REJECTED_SCORING_STAGE and any other value are rejected
   distinctly), an exact approver match against the spec's approver_required,
   approved_at strictly before an optional first_scoring_execution_at, a
   separately supplied observed_runtime_identity (worker_designation,
   worker_arch, bias_commit, nirvana_banner -- never copied from the
   record), and always independently recomputes resource_manifest_sha256
   from resource_manifest_checksums_dir via compute_resource_manifest_sha256.
   The claimed x64_freeze block must equal that observed+recomputed
   identity byte-for-byte; a well-formed but fabricated digest (including
   all-zero) can never pass.

2. adjudicate_prospective_outcomes now calls validate_scoring_stage_approval
   unconditionally as its first action. No outcome -- including PASS or
   AUTHORIZED_RESEARCH_ONLY -- can be produced without a verified
   scoring-stage approval and matching observed runtime identity.

3. The executable acquisition CLI (run_clinvar_2026_08_prospective_freeze.py)
   no longer accepts --transport-factory. Production/executable transport is
   hard-wired to prospective_exact_source_transport.build_transport() with no
   arguments, so no CLI option, config value, or environment variable can
   substitute a different transport for a confirmed live --execute run.

4. Acquisition (validate_pre_data_approval / execute_transport_and_raw_freeze)
   no longer verifies scoring_semantics_projection_sha256 or any base eval
   config hash. That check belongs solely to merge_prospective_overlay,
   which is no longer called from the acquisition path at all -- only from
   the (unchanged) scoring-stage config-build surface.

Updates the two draft approval JSON files and the registration spec's
pre_data_approval / scoring_stage_approval sections to match, and adds
targeted positive/negative tests (fabricated/all-zero digest, wrong
approver, REJECTED decision, mistimed approval, missing gate at
adjudication, alternate live transport, scoring-config drift not blocking
acquisition).

Validated in WSL2 raptor venv (/home/sdrona/raptor/bin/python, Python
3.12.3): targeted test files pass (194 passed, 3 skipped), and the broader
tests/eval suite passes except 2 pre-existing failures in
test_tiered_readjudication_frozen_r2.py that are unrelated to this change
(file is byte-identical to origin/main).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7c146921-f3dd-4a1e-8cf0-8f574de49204
…scoring boundary (round 4)

Additive fixes on top of 323cebe, none amended:

1. Hard-wire confirmed live execution against caller-selected code
   before the real GET: capture_transport_identity_pin is captured in
   scripts/run_clinvar_2026_08_prospective_freeze.py immediately after
   build_transport(), before any lookup-module resolution, and
   execute_transport_and_raw_freeze re-verifies the pin both on entry
   and immediately before the network stream, rejecting
   TRANSPORT_IDENTITY_TAMPERED if a caller-selected lookup callable
   monkeypatches the transport class mid-call.
2. validate_scoring_stage_approval now takes explicit observation
   probes (worker designation/arch, BIAS commit, Nirvana banner,
   resource-manifest checksums dir) instead of trusting a caller-
   supplied observed_runtime_identity mapping, and independently
   recomputes resource_manifest_sha256/immutable_inputs_verified
   rather than accepting the claimed values.
3. adjudicate_prospective_outcomes now requires an immutable
   first_scoring_execution_at input and rejects PASS/
   AUTHORIZED_RESEARCH_ONLY when it is missing, future-dated, or not
   strictly after approved_at, closing the post-scoring/future-
   approval gap.
4. Pre-data implementation_freeze validation now also verifies the
   ACTUALLY EXECUTING module bytes in this process
   (_implementation_freeze_executing_code_failure), not just the
   named historical commit's git-show content.
5. immutable_inputs_verified (tsc2.yaml, ACMG/BIAS policy, masking/
   predictor-aggregation config, ...) moved out of pre_data_approval
   and into scoring_stage_approval only; acquisition no longer
   requires or references any scoring-stage config.
6. Regenerated the pre_data draft's registration.git_blob_sha1/
   canonical_lf_sha256 against the current spec bytes, added
   immutable_inputs_verified + evidence to the scoring_stage draft,
   and added executing-commit re-pin guidance for final approval.

Updated docs/ops/adr-0008-resource-manifest-digest.md and the
registration spec YAML to document all six boundary/verification
changes; updated all three protected red test files (freeze,
adjudication, harness) plus the shared red-test helper module with
negative regression tests for each finding.

Validated in WSL2 raptor venv (/home/sdrona/raptor/bin/python,
Python 3.12.3): targeted suite (freeze_red + adjudication_red +
harness_red) 177 passed, 3 skipped; broader tests/eval suite 879
passed, 9 skipped, 2 pre-existing failures in
test_tiered_readjudication_frozen_r2.py unrelated to this change
(untouched file, fails identically in isolation on this host).

No ClinVar/BIAS/Nirvana access performed. PR #26 remains draft.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… live GET

Independent review (round 5) found that despite the round-4 transport-
identity-pin defense, scripts/run_clinvar_2026_08_prospective_freeze.py's
confirmed --execute path still dynamically imported caller-selected
--published-archive-date-lookup/--official-md5-lookup module:callable
specs via importlib.import_module, AFTER build_transport() but BEFORE the
real GET. Importing any caller-selected module in that window is an
arbitrary-code-execution surface in its own right (it can monkeypatch
transport internals, issue its own GET, replace
execute_transport_and_raw_freeze, or mutate unrelated process state)
regardless of the identity pin.

- Add src/raptor/eval/prospective_exact_source_metadata_lookups.py: the
  ONE production-owned, statically-imported implementation of both
  published_archive_date_lookup/official_md5_lookup ports. Both are
  permanent fail-closed stubs (MetadataLookupNotYetImplementedError)
  since neither port's real NCBI source has ever been ratified by this
  contract; a real implementation must land here via a dedicated,
  reviewed follow-up, never via a CLI-selected substitute.
- scripts/run_clinvar_2026_08_prospective_freeze.py: remove
  _resolve_import_target and both --published-archive-date-lookup/
  --official-md5-lookup CLI options entirely (unrecognized-arguments
  failure now); replace with static attribute references to the new
  module. Confirmed live --execute now imports/executes zero
  caller-selected Python/plugin code before the real GET. The transport-
  identity pin is retained as defense-in-depth only.
- src/raptor/eval/prospective_freeze.py: wrap both lookup-port calls in
  execute_transport_and_raw_freeze in try/except, converting any raised
  exception (including the new hard-wired stub's) into a typed
  BLOCKED/INVALID result (PUBLISHED_ARCHIVE_DATE_LOOKUP_FAILED /
  OFFICIAL_MD5_LOOKUP_FAILED) instead of a raw propagated traceback;
  docstrings updated to reflect the closed vulnerability.
- tests/eval/test_clinvar_2026_08_prospective_harness_red.py: replace the
  dynamic-import test fixtures with direct monkeypatching of the new
  module's attributes; delete tests for the now-nonexistent
  module:callable resolution/tamper-via-import mechanism; add
  test_execute_rejects_obsolete_lookup_injection_cli_options (both
  removed options are unrecognized-argument argparse failures with zero
  transport/import/network activity) and
  test_execute_hard_wired_metadata_lookups_reached_with_no_caller_selected_import
  (real end-to-end --execute reaches the hard-wired stub with
  import_calls == [] throughout); flip import_calls != [] -> == [] at
  every remaining call site now that no dynamic import path exists.

Validation (WSL2 raptor venv, /home/sdrona/raptor/bin/python 3.12.3):
- tests/eval/test_clinvar_2026_08_prospective_harness_red.py: 30 passed
- freeze_red + harness_red + adjudication_red combined: 172 passed, 3 skipped
- tests/eval full suite: 874 passed, 9 skipped, 2 failed (pre-existing,
  environment-specific: test_tiered_readjudication_frozen_r2.py
  ::test_cli_happy_path requires a clean git worktree and
  ::test_cli_concurrent_publication_fail_closed; unrelated to this change)

No ClinVar/BIAS/Nirvana access. PR #26 remains draft.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7c146921-f3dd-4a1e-8cf0-8f574de49204
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7c146921-f3dd-4a1e-8cf0-8f574de49204
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7c146921-f3dd-4a1e-8cf0-8f574de49204
@srinivasdrona
srinivasdrona marked this pull request as ready for review September 1, 2026 13:19
@srinivasdrona
srinivasdrona merged commit 831d146 into main Sep 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant