From 8bb9dfee92d9fcc6f150a4b44057301640e2300c Mon Sep 17 00:00:00 2001 From: Tcx086 <957658986chen@gmail.com> Date: Sun, 26 Jul 2026 05:54:48 -0400 Subject: [PATCH 1/2] feat: add deterministic historical shadow campaign --- .gitattributes | 1 + .github/workflows/ci.yml | 19 +- .project/implementation_status.json | 8 +- README.md | 11 +- docs/IMPLEMENTATION_STATUS.md | 9 +- docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md | 242 ++ open-core/pyproject.toml | 1 + ...e8b_historical_shadow_campaign_evidence.py | 134 + .../live/shadow_campaign_accounts.py | 387 +++ .../live/shadow_campaign_cli.py | 1159 +++++++++ .../live/shadow_campaign_corpus.py | 639 +++++ .../live/shadow_campaign_evidence.py | 664 +++++ .../live/shadow_campaign_expected_hashes.py | 313 +++ .../live/shadow_campaign_models.py | 864 ++++++ .../live/shadow_campaign_runtime.py | 1991 ++++++++++++++ .../live/shadow_campaign_verifier.py | 2314 +++++++++++++++++ .../live/shadow_campaign_verifier_runtime.py | 633 +++++ .../live/shadow_repository.py | 712 ++++- .../live/shadow_runtime.py | 230 +- .../live/shadow_verifier.py | 17 +- .../src/secure_eval_wrapper/validation.py | 55 +- .../test_phase8b_shadow_campaign_antileak.py | 289 ++ ...test_phase8b_shadow_campaign_boundaries.py | 619 +++++ .../tests/test_phase8b_shadow_campaign_cli.py | 639 +++++ ...est_phase8b_shadow_campaign_concurrency.py | 75 + .../test_phase8b_shadow_campaign_corpus.py | 394 +++ .../test_phase8b_shadow_campaign_crash.py | 66 + .../test_phase8b_shadow_campaign_evidence.py | 817 ++++++ .../test_phase8b_shadow_campaign_manifest.py | 198 ++ .../test_phase8b_shadow_campaign_mutation.py | 326 +++ .../test_phase8b_shadow_campaign_postgres.py | 566 ++++ .../test_phase8b_shadow_campaign_replay.py | 402 +++ .../test_phase8b_shadow_campaign_restart.py | 329 +++ .../test_phase8b_shadow_campaign_runtime.py | 551 ++++ ...hase8b_shadow_campaign_verifier_runtime.py | 115 + .../tests/test_phase8b_shadow_postgres.py | 6 +- 36 files changed, 15755 insertions(+), 40 deletions(-) create mode 100644 .gitattributes create mode 100644 docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md create mode 100644 open-core/scripts/generate_phase8b_historical_shadow_campaign_evidence.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_accounts.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_cli.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_corpus.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_evidence.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_expected_hashes.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_models.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_runtime.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier.py create mode 100644 open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier_runtime.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_antileak.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_boundaries.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_cli.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_concurrency.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_corpus.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_crash.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_evidence.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_manifest.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_mutation.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_postgres.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_replay.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_restart.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_runtime.py create mode 100644 open-core/tests/test_phase8b_shadow_campaign_verifier_runtime.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d304e68 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +docs/evidence/phase8b_historical_shadow_campaign_public.json text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87365b0..b80ad4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,18 @@ jobs: run: python -m unittest discover -s open-core/tests -p "test_phase8b_operator_bootstrap.py" -v - name: Phase 8B socket-free shadow runtime, account/market matrices, restart, replay, concurrency, crash, boundaries, and evidence run: python -m unittest discover -s open-core/tests -p "test_phase8b_shadow*.py" -v + - name: Phase 8B historical shadow campaign compact 1,440-event cross-platform verification + if: ${{ matrix.os == 'windows-latest' || matrix.python != '3.12' }} + env: + RUN_SHADOW_CAMPAIGN_COMPACT: "true" + run: python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_campaign*.py" -v + - name: Phase 8B historical shadow campaign full 25,920-event verifier and evidence reproducibility + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python == '3.12' }} + env: + RUN_SHADOW_CAMPAIGN_FULL: "true" + run: | + python -c "import subprocess; from secure_eval_wrapper.live.shadow_campaign_evidence import load_public_historical_shadow_campaign_evidence as load; evidence=load('docs/evidence/phase8b_historical_shadow_campaign_public.json'); sha=evidence['repository_sha']; subprocess.run(['git','merge-base','--is-ancestor',sha,'HEAD'],check=True); changed=subprocess.check_output(['git','diff','--name-only',sha,'HEAD'],text=True).splitlines(); assert changed == ['docs/evidence/phase8b_historical_shadow_campaign_public.json'], changed" + python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_campaign*.py" -v - name: Compile package and scripts run: python -m compileall -q open-core/src open-core/scripts - run: secure-eval-backtest @@ -105,6 +117,7 @@ jobs: - run: secure-eval-live-kill --help - run: secure-eval-live-bootstrap --help - run: secure-eval-live-shadow --help + - run: secure-eval-live-shadow-campaign --help postgres-integration: name: PostgreSQL 16 integration @@ -199,6 +212,10 @@ jobs: env: RUN_POSTGRES_INTEGRATION: "true" run: python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_postgres.py" -v + - name: Phase 8B historical shadow campaign PostgreSQL 2,016-event persistence, replay, resume, gaps, conflicts, corruption, concurrency, and crash recovery + env: + RUN_POSTGRES_INTEGRATION: "true" + run: python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_campaign_postgres.py" -v - name: Seeded 0023 to 0026 upgrade with existing Phase 5, Phase 6, Phase 7, and Phase 8A rows run: | python open-core/scripts/apply_postgres_migrations.py --database secure_eval_upgrade --create-database --through 0009 --seed-phase5 @@ -234,4 +251,4 @@ jobs: python-version: "3.12" - run: python -m pip install -e "./open-core[test]" - name: No production writes, credentials, arbitrary endpoints, account powers, flattening, FIX, or fixture preflight authority - run: python open-core/src/secure_eval_wrapper/validation.py --skip-tests && python -m unittest discover -s open-core/tests -p "test_phase8_credential_permissions.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8_identity.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8_guarded_live.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8b_authenticated_readonly_preflight.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_boundaries.py" -v + run: python open-core/src/secure_eval_wrapper/validation.py --skip-tests && python -m unittest discover -s open-core/tests -p "test_phase8_credential_permissions.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8_identity.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8_guarded_live.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8b_authenticated_readonly_preflight.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_boundaries.py" -v && python -m unittest discover -s open-core/tests -p "test_phase8b_shadow_campaign_boundaries.py" -v diff --git a/.project/implementation_status.json b/.project/implementation_status.json index f8bbeaf..0e0ee52 100644 --- a/.project/implementation_status.json +++ b/.project/implementation_status.json @@ -4,7 +4,7 @@ "repository": "Tcx086/secure-eval-wrapper", "status_source": "docs/IMPLEMENTATION_STATUS.md", "schema": ".project/implementation_status.schema.json", - "updated_at_utc": "2026-07-18T18:00:00Z", + "updated_at_utc": "2026-07-26T00:00:00Z", "current_phase": "phase_8_guarded_live_execution", "rules": { "future_functional_prs_must_update_markdown_status": true, @@ -412,9 +412,13 @@ "Persist complete fixture, public, and unavailable provenance plus summary hashes inside the authoritative JSONB bundle; apply one canonical SQL, JSON, hash, and safety validator during target verification, replay, load, and inspect; and add committed-row, restart, replay, conflict, and provenance-tamper regressions without migration 0027", "Upgrade to verifier v4 with seven distinct executable concurrency semantics whose expected and observed classifications, run IDs, hashes, result hashes, and passed flags are rerun and compared exactly", "Preserve source-issued public read counts across downstream runtime, persistence, replay and conflict, and serialization failures with an immutable public-safe operation carrier and CLI regressions that cannot expose private exception text", - "Independently audit and accept the public-data and synthetic-account Phase 8B shadow-assurance implementation with audit conclusion PASS and 0 blockers: PR #9 candidate head 3e2849f0cc6262b75cb983a9876e19cf7c5356d9 merged as 376aa35e5b8b77d67b24efca5bbc9fffc95137a5; accept docs/evidence/phase8b_shadow_assurance_public.json payload SHA-256 666c0e229c9b002a78ee5b235f11eef42fa3d0403b9169c2528c0ba30c8ae0dd and phase8b-shadow-assurance-verifier-v4 result SHA-256 a90b6730d527d42527be697d498c6e8c0fb0793a94bc0c03f84d0a743ad6542a; final-main Actions run 29865978929 checked out 376aa35e5b8b77d67b24efca5bbc9fffc95137a5 and passed Ubuntu Python 3.11 job 88754184792, Ubuntu Python 3.12 job 88754184786, Ubuntu Python 3.13 job 88754184811, Windows Python 3.12 job 88754184779, PostgreSQL 16 integration job 88754184783, and public/private and runtime boundary job 88754184785; migrations 0001 through 0026 remain immutable with no 0027; no real public-network smoke, operator bootstrap, or authenticated proof was executed, real-proof authorization remains NO, and production submit/cancel remain disabled and unreachable" + "Independently audit and accept the public-data and synthetic-account Phase 8B shadow-assurance implementation with audit conclusion PASS and 0 blockers: PR #9 candidate head 3e2849f0cc6262b75cb983a9876e19cf7c5356d9 merged as 376aa35e5b8b77d67b24efca5bbc9fffc95137a5; accept docs/evidence/phase8b_shadow_assurance_public.json payload SHA-256 666c0e229c9b002a78ee5b235f11eef42fa3d0403b9169c2528c0ba30c8ae0dd and phase8b-shadow-assurance-verifier-v4 result SHA-256 a90b6730d527d42527be697d498c6e8c0fb0793a94bc0c03f84d0a743ad6542a; final-main Actions run 29865978929 checked out 376aa35e5b8b77d67b24efca5bbc9fffc95137a5 and passed Ubuntu Python 3.11 job 88754184792, Ubuntu Python 3.12 job 88754184786, Ubuntu Python 3.13 job 88754184811, Windows Python 3.12 job 88754184779, PostgreSQL 16 integration job 88754184783, and public/private and runtime boundary job 88754184785; migrations 0001 through 0026 remain immutable with no 0027; no real public-network smoke, operator bootstrap, or authenticated proof was executed, real-proof authorization remains NO, and production submit/cancel remain disabled and unreachable", + "Implement the historical shadow campaign candidate as a deterministic streaming orchestration layer over the accepted ShadowAssuranceRuntime, guarded-live configuration, standardized signals, preflight, approval, manifest, risk, reservation, canonical shadow bundle, and PostgreSQL repository contracts; the fixed full profile generates 25,920 five-minute BTC-USDT Spot events across twelve regimes and a deterministic synthetic-account timeline without treating hypothetical intents as fills or account-state changes", + "Add deterministic event, segment, window, account-snapshot, run, decision-chain, and campaign identities; point-in-time anti-lookahead checks; exact replay; earliest-gap resume; mutation lineage; concurrency and crash matrices; the secure-eval-live-shadow-campaign plan, run, resume, inspect, and verify interface; callback-free exact-type and sealed-container boundaries; truthful structured failure stages and progress; strict canonical duplicate-free verifier-derived public evidence; focused tests; and six-job CI wiring without adding a migration or production authority; this records an implementation candidate only and does not claim that independent audit, branch CI, PostgreSQL execution, public-network smoke, operator bootstrap, or authenticated proof has passed or executed", + "Preserve the independently accepted Phase 8B shadow-assurance baseline unchanged; operator bootstrap execution remains unexecuted, authenticated proof remains unexecuted, real-proof authorization remains NO, Phase 8 remains in_progress, Phase 8C remains not_started, Phase 9 remains todo, and production submit and cancel remain disabled and unreachable" ], "todo": [ + "Independently audit the Phase 8B historical shadow campaign implementation and its public evidence before accepting it; until then it remains implemented_pending_independent_audit", "Prepare a separately and explicitly authorized local secure_eval_phase8b database bootstrap operation; bootstrap authorization and authenticated-proof authorization must remain distinct, and neither bootstrap nor authenticated proof may run automatically", "Keep real authenticated proof authorization at NO; optionally execute exactly one controlled local authenticated read-only proof with operator-owned environment credentials that are never persisted only after separate exact operator authorization", "Independently review the resulting redacted proof before accepting the operational Phase 8B checkpoint", diff --git a/README.md b/README.md index 369c7bd..98ced51 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A public, auditable, and reproducible framework for building crypto trading syst The project is developed in explicit, auditable phases. Architecture, PostgreSQL foundations, public market data, public alpha, and standardized signals are complete. Deterministic simulated execution and event-driven backtesting are complete after local PostgreSQL 16 and independent GitHub Actions validation. -> **Current status:** Phases 0-7 are completed checkpoints. Phase 8A and the Phase 8B authenticated read-only proof implementation are independently audited and accepted. A public-data/synthetic-account Phase 8B shadow-assurance implementation candidate is pending independent audit; the optional real local authenticated proof has not been executed. Production submit/cancel and external production FIX remain disabled and unreachable. +> **Current status:** Phases 0-7 are completed checkpoints. Phase 8A, the Phase 8B authenticated read-only proof implementation, the dedicated operator bootstrap implementation, and the public-data/synthetic-account shadow-assurance baseline are independently audited and accepted. The Phase 8B historical shadow campaign is an implementation candidate pending independent audit. Operator bootstrap execution and the optional real authenticated proof remain unexecuted, real-proof authorization is `NO`, and production submit/cancel and external production FIX remain disabled and unreachable. Phase 8 remains `in_progress`, Phase 8C remains `not_started`, and Phase 9 remains `todo`. ## Why this project exists @@ -73,8 +73,8 @@ Signals are not fills. Phase 5 backtests create order intents, pass them through | 5 | Simulated Execution and Event-Driven Backtesting | Completed; PostgreSQL and CI validated | | 6 | Monitoring and strictly simulated FIX 4.4-compatible profile | Completed; first-independent-audit repairs accepted | | 7 | Paper Trading | Completed through fifth independent audit | -| 8 | Guarded Live Execution | Phase 8A/proof accepted; shadow-assurance candidate pending audit; real proof unexecuted; production writes disabled | -| 9 | Reporting and Public Delivery | Future | +| 8 | Guarded Live Execution | In progress; Phase 8A/proof/bootstrap/shadow-assurance implementations accepted; historical campaign candidate pending audit; bootstrap and real proof unexecuted; production writes disabled; Phase 8C not started | +| 9 | Reporting and Public Delivery | Todo; not started | The authoritative progress records are: @@ -83,6 +83,7 @@ The authoritative progress records are: - [`docs/IMPLEMENTATION_STATUS.md`](docs/IMPLEMENTATION_STATUS.md) - [Guarded live execution](docs/GUARDED_LIVE_EXECUTION.md) - [Phase 8B shadow assurance](docs/PHASE8B_SHADOW_ASSURANCE.md) +- [Phase 8B historical shadow campaign](docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md) - [`.project/implementation_status.json`](.project/implementation_status.json) Completed and planned work must remain synchronized between these two files. @@ -340,7 +341,7 @@ The intended principle is simple: **make the infrastructure inspectable without Phases 3 and 4 are complete and auditable: public alphas produce continuous point-in-time `AlphaValue` records, and standardized signals apply deterministic ranking, thresholding, combination, conflict, and confidence rules with PostgreSQL lineage. -Phase 5 simulated execution and backtesting is complete through its fourth independent audit. Phase 6 monitoring and the strictly simulated FIX API are complete. Phase 7 safe paper trading is complete through its fifth independent audit. Phase 8A is an accepted PostgreSQL-authoritative guarded-live dry-run/read-only foundation. The Phase 8B explicit authenticated read-only proof implementation is independently audited and accepted, while its optional real local authenticated proof has not yet been executed; production order and cancellation transport remains unconditionally disabled. +Phase 5 simulated execution and backtesting is complete through its fourth independent audit. Phase 6 monitoring and the strictly simulated FIX API are complete. Phase 7 safe paper trading is complete through its fifth independent audit. Phase 8A is an accepted PostgreSQL-authoritative guarded-live dry-run/read-only foundation. The Phase 8B authenticated read-only proof, operator bootstrap, and public-data shadow-assurance implementations are accepted baselines. The historical shadow campaign is a separate implementation candidate pending independent audit; neither operator bootstrap execution nor the optional real authenticated proof has occurred, real-proof authorization remains `NO`, and production order/cancellation transport remains unconditionally disabled. ## Disclaimer @@ -359,3 +360,5 @@ The public framework includes deterministic point-in-time monitoring and a stric `secure-eval-live-dry-run`, `secure-eval-live-status`, `secure-eval-live-reconcile`, and `secure-eval-live-kill` remain safe, write-free commands. `secure-eval-live-preflight` is socket-free without its explicit Phase 8B network flag; with every reviewed configuration, identity, fingerprint, credential-source, PostgreSQL, and CI gate satisfied, it can issue only the exact six catalogued OKX GETs and persist a public-safe proof. No Phase 8 command calls production submit or cancel. See [Guarded Live Execution](docs/GUARDED_LIVE_EXECUTION.md). `secure-eval-live-shadow` is a separate, permanently hypothetical assurance path. Its default fixture mode evaluates deterministic synthetic accounts and replayable public-market fixtures through the shared guarded-live preflight, approval, manifest, risk, reservation, and PostgreSQL audit contracts. Public-network mode is explicit, bounded, unauthenticated, and limited to exactly two OKX BTC-USDT Spot public GETs. The production source always constructs its own sealed `UrlLibHttpTransport`; offline fake transports remain confined to a `fixture_protocol_test` request-contract harness and cannot create public-network authority. Source-instance-bound provenance is persisted inside the decision/summary hash chain. One canonical validator rechecks every existing, replayed, loaded, or inspected bundle and fails closed on committed-row tampering without repair. Verifier v4 executes the seven distinct concurrency semantics rather than repeating one idempotency pattern. Persistent runs accept only literal loopback and disposable `secure_eval_phase8b_shadow_` PostgreSQL 16 targets, with libpq-managed authentication and no password CLI argument. The checked public artifact reports both PostgreSQL verification and public smoke as not executed and remains pending independent audit. The shadow dependency graph has no production broker, submit, cancel, credentials, authenticated endpoints, or operator-database authority. See [Phase 8B Shadow Assurance](docs/PHASE8B_SHADOW_ASSURANCE.md). + +`secure-eval-live-shadow-campaign` extends that accepted shadow-assurance baseline with a deterministic, streaming 90-day-equivalent historical campaign over 25,920 generated five-minute BTC-USDT Spot events, twelve fixed regimes, and a deterministic synthetic-account timeline. `plan` is socket-free and database-free; `run`, `resume`, `inspect`, and `verify` accept only literal loopback PostgreSQL 16 targets named `secure_eval_phase8b_shadow_campaign_`, delegate authentication to libpq, and expose no password argument. Completion is reconstructed from canonical validated per-event shadow bundles in `audit.run_manifests`; no migration `0027` or campaign-complete authority row is introduced. The candidate enforces point-in-time decision inputs, deterministic IDs and hash chains, exact replay, earliest-gap resume, mutation lineage, and bounded public-safe output. It is pending independent audit and does not authorize authenticated proof or production writes. See [Phase 8B Historical Shadow Campaign](docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md). diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 0146249..1c48072 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -9,7 +9,7 @@ Every future functional PR must update both files in the same change: Completed work must be listed under `Completed`. Everything not done must remain under `Todo`. -Current phase: `phase_8_guarded_live_execution` (`in_progress`). Phase 8A guarded-live dry-run/read-only runtime, the Phase 8B authenticated read-only proof implementation, the dedicated Phase 8B local operator bootstrap implementation, and the public-data/synthetic-account Phase 8B shadow-assurance implementation are independently audited and accepted. The real authenticated proof and operator bootstrap remain unexecuted and real-proof authorization remains NO. Phase 8 remains `in_progress`, Phase 8C is not started, Phase 9 remains todo, and production submit/cancel remain disabled and unreachable. +Current phase: `phase_8_guarded_live_execution` (`in_progress`). Phase 8A guarded-live dry-run/read-only runtime, the Phase 8B authenticated read-only proof implementation, the dedicated Phase 8B local operator bootstrap implementation, and the public-data/synthetic-account Phase 8B shadow-assurance implementation are independently audited and accepted. The Phase 8B historical shadow campaign is an implementation candidate pending independent audit. The real authenticated proof and operator bootstrap remain unexecuted and real-proof authorization remains NO. Phase 8 remains `in_progress`, Phase 8C is not started, Phase 9 remains todo, and production submit/cancel remain disabled and unreachable. ## Non-Negotiable Constraints - PostgreSQL is the only authoritative storage layer. @@ -374,6 +374,12 @@ Current phase: `phase_8_guarded_live_execution` (`in_progress`). Phase 8A guarde - [x] Preserve source-issued public read counts across downstream runtime, persistence, replay/conflict, and serialization failures with an immutable public-safe operation carrier and CLI regressions that cannot expose private exception text. - [x] Independently audit and accept the public-data/synthetic-account Phase 8B shadow-assurance implementation with audit conclusion PASS and 0 blockers: PR `#9` candidate head `3e2849f0cc6262b75cb983a9876e19cf7c5356d9` merged as `376aa35e5b8b77d67b24efca5bbc9fffc95137a5`; accept `docs/evidence/phase8b_shadow_assurance_public.json` payload SHA-256 `666c0e229c9b002a78ee5b235f11eef42fa3d0403b9169c2528c0ba30c8ae0dd` and `phase8b-shadow-assurance-verifier-v4` result SHA-256 `a90b6730d527d42527be697d498c6e8c0fb0793a94bc0c03f84d0a743ad6542a`; final-main Actions run `29865978929` checked out `376aa35e5b8b77d67b24efca5bbc9fffc95137a5` and passed Ubuntu Python 3.11 job `88754184792`, Ubuntu Python 3.12 job `88754184786`, Ubuntu Python 3.13 job `88754184811`, Windows Python 3.12 job `88754184779`, PostgreSQL 16 integration job `88754184783`, and public/private and runtime boundary job `88754184785`; migrations `0001` through `0026` remain immutable with no `0027`; no real public-network smoke, operator bootstrap, or authenticated proof was executed, real-proof authorization remains NO, and production submit/cancel remain disabled and unreachable. +### Phase 8B: historical shadow campaign (implementation candidate; pending independent audit) + +- [x] Implement the historical shadow campaign candidate as a deterministic streaming orchestration layer over the accepted `ShadowAssuranceRuntime`, guarded-live configuration, standardized signals, preflight, approval, manifest, risk, reservation, canonical shadow bundle, and PostgreSQL repository contracts. The fixed full profile generates 25,920 five-minute BTC-USDT Spot events across twelve regimes and a deterministic synthetic-account timeline without treating hypothetical intents as fills or account-state changes. +- [x] Add deterministic event, segment, window, account-snapshot, run, decision-chain, and campaign identities; point-in-time anti-lookahead checks; exact replay; earliest-gap resume; mutation lineage; concurrency and crash matrices; the `secure-eval-live-shadow-campaign` plan/run/resume/inspect/verify interface; callback-free exact-type and sealed-container boundaries; truthful structured failure stages and progress; strict canonical duplicate-free verifier-derived public evidence; focused tests; and six-job CI wiring without adding a migration or production authority. This records an implementation candidate only and does not claim that independent audit, branch CI, PostgreSQL execution, public-network smoke, operator bootstrap, or authenticated proof has passed or executed. +- [x] Preserve the independently accepted Phase 8B shadow-assurance baseline unchanged. Operator bootstrap execution remains unexecuted, authenticated proof remains unexecuted, real-proof authorization remains NO, Phase 8 remains `in_progress`, Phase 8C remains not started, Phase 9 remains `todo`, and production submit/cancel remain disabled and unreachable. + ## Todo ### Future provider enhancements @@ -383,6 +389,7 @@ Current phase: `phase_8_guarded_live_execution` (`in_progress`). Phase 8A guarde ### Phase 8: guarded live execution (remaining) - [ ] Prepare a separately and explicitly authorized local `secure_eval_phase8b` database bootstrap operation; bootstrap authorization and authenticated-proof authorization must remain distinct, and neither bootstrap nor authenticated proof may run automatically. +- [ ] Independently audit the Phase 8B historical shadow campaign implementation and its public evidence before accepting it; until then it remains `implemented_pending_independent_audit`. - [ ] Keep real authenticated proof authorization at NO; optionally execute exactly one controlled local authenticated read-only proof with operator-owned environment credentials that are never persisted only after separate exact operator authorization. - [ ] Independently review the resulting redacted proof before accepting the operational Phase 8B checkpoint. - [ ] Phase 8C is not started; do not design it until the operational Phase 8B checkpoint is separately accepted. diff --git a/docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md b/docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md new file mode 100644 index 0000000..35a764b --- /dev/null +++ b/docs/PHASE8B_HISTORICAL_SHADOW_CAMPAIGN.md @@ -0,0 +1,242 @@ +# Phase 8B Historical Shadow Campaign + +## Status + +The historical shadow campaign is a Phase 8B implementation candidate pending independent audit. It extends the independently accepted public-data and synthetic-account shadow-assurance baseline; it does not replace or reopen that accepted baseline. + +This candidate does not establish an operational checkpoint, authorize a real authenticated proof, start Phase 8C or Phase 9, or enable production trading. Operator bootstrap execution and authenticated proof remain unexecuted, real-proof authorization remains `NO`, Phase 8 remains `in_progress`, Phase 8C remains not started, Phase 9 remains `todo`, and production submit/cancel remain disabled and unreachable. + +The campaign measures deterministic decision and safety behavior under a long generated sequence. It is not a backtest executor and makes no claim about profit, expected return, Sharpe ratio, strategy edge, or real trading performance. + +## Safety boundary + +The campaign may use only repository-owned deterministic corpus specifications, generated public-market sequences, synthetic accounts, the accepted guarded-live policy contracts, memory test repositories, disposable PostgreSQL 16 campaign databases, and public-safe aggregate evidence. + +It must never: + +- read exchange credentials, secrets, passphrases, real fingerprints, UIDs, balances, positions, orders, trade logs, or private strategy parameters; +- call an authenticated or private endpoint; +- access `secure_eval_phase8b`, `secure_eval_wrapper`, another operator database, or a non-loopback database host; +- construct or call production submit, cancel, withdrawal, transfer, borrow, leverage, derivative, flatten, or FIX transport; +- turn a hypothetical intent into a fill or mutate an account snapshot from that intent; +- modify migrations `0001` through `0026` or add migration `0027`. + +Every campaign intent remains permanently hypothetical and non-routable. Network writes, production transport calls, authenticated endpoint calls, credential reads, and production writes must remain zero. Production submit and cancel must remain unreachable. + +## Architecture + +The campaign is a streaming orchestration layer around the accepted `ShadowAssuranceRuntime`: + +```text +immutable campaign spec + fixed seed + | + v +streaming historical corpus generator ---- deterministic synthetic account timeline + | | + +------------------+-------------------+ + v + existing ShadowAssuranceRuntime + configuration -> signal -> preflight -> approval -> manifest + -> risk -> reservation -> shadow intent + | + v + canonical Phase 8B shadow bundle + | + +--------------+--------------+ + | | + memory test repository audit.run_manifests + | + v + canonical row/bundle validation + | + v + reconstructed progress, hashes, and evidence +``` + +The campaign does not implement a simplified trading decision path. Each event uses the shared configuration, standardized signal, preflight, approval, manifest, live-risk, reservation, bundle, validator, and repository contracts. An accepted shadow intent is recorded as a decision result only; the campaign generates no fills and does not update simulated or real positions. + +### Authority boundary versus policy evaluation + +Authority denial and policy evaluation are separate layers. Before event zero, the campaign module graph, exact repository type, repository attributes, persistent repository identity, and generated-scenario payload are checked for broker, transport, credential, authenticated endpoint, operator-database, account-power, derivative, production FIX, submit, and cancel authority. Imported module names, imported symbols and aliases, dynamic-import calls, callable dependencies, and nested case variants are part of that structural boundary. + +Only after those authority checks may the shared shadow runtime evaluate an expectation-free generated scenario. That policy layer evaluates public/synthetic market, account, request, preflight, approval, risk, reservation, and hypothetical-intent facts. A policy blocker is a decision outcome; it is not permission to add an authority dependency. Regime labels and expected-result or expected-blocker metadata remain test-oracle data and cannot enter the runtime scenario under spelling or case variants. + +The CLI output contract is a third, disclosure-only boundary. It cannot grant runtime authority and does not substitute for either structural authority denial or guarded policy evaluation. + +## Deterministic corpus + +The fixed full profile is `full-90d`: + +| Property | Value | +|---|---:| +| Instrument | `BTC-USDT` Spot | +| Timeframe | 5 minutes | +| Equivalent duration | 90 days | +| Event count | 25,920 | +| Segment count | 12 | +| Events per segment | 2,160 | +| 1-day window | 288 events | +| 7-day window | 2,016 events | +| 30-day window | 8,640 events | +| 90-day window | 25,920 events | + +The repository stores a compact specification, generator version, fixed seeds, segment specifications, and expected hashes. It does not commit a 25,920-row market-data file. Events are generated on demand in canonical event order. + +The twelve fixed segments cover: + +1. quiet range; +2. low-volatility uptrend; +3. low-volatility downtrend; +4. high-volatility uptrend; +5. high-volatility downtrend; +6. volatility spike; +7. flash drawdown with bounded rebound; +8. deterministic gaps; +9. illiquid and wide-spread conditions; +10. stale, missing, duplicate, and out-of-order observations; +11. instrument-metadata boundary churn; +12. recovery and normalization. + +Every event and segment has a stable ID and canonical hash. The campaign spec, generator implementation, ordered event sequence, segments, and reporting windows are independently hash-bound. For the same spec, seed, and repository SHA, generation is byte-deterministic and independent of process identity, thread completion order, machine identity, database OID, local path, and wall-clock time. + +The cross-platform compact profile uses exactly 1,440 events from the same generator/spec family and checks a fixed subset hash. It is a portability gate, not a substitute for the full profile. + +## Synthetic account timeline + +Account snapshots are deterministic risk inputs and always carry `synthetic_account = true`. The fixed timeline covers clean flat state, near-limit notional, low quote and base balances, existing Spot inventory, pending buy and sell pressure, reserved-notional pressure, daily-loss near-threshold and breached states, active kill switch, and recovery to a clean synthetic state. + +Each snapshot has a stable snapshot ID, canonical hash, effective event range, synthetic classification, and expected blocker metadata. Expected blocker metadata is test oracle information and is not a runtime decision input. A shadow intent never changes a later account snapshot. + +## Streaming and anti-lookahead + +Corpus processing is iterator-based and uses bounded rolling state. The campaign does not permanently materialize all 25,920 event objects in memory, and any worker queue is bounded. + +For event index `i`, a decision may observe only events `0..i` and the account snapshot effective at `i`. It cannot inspect event `i+1`, a future segment, future account state, full-sample statistics, regime labels, expected results, or mutation suffixes. Rolling calculations use historical windows only. + +Executable anti-lookahead cases require: + +- changing the final future event leaves all earlier decision hashes unchanged; +- changing the next segment leaves the current segment hash unchanged; +- truncation at event `N` produces the same first `N` decisions as the full campaign; +- future account mutations leave historical decisions unchanged; +- expected-blocker metadata cannot affect runtime output; +- a different generation traversal that yields the same canonical event order produces the same result. + +## Identity and hash chain + +A deterministic event run ID binds the campaign ID, event index, event hash, synthetic account snapshot hash, repository SHA, and campaign spec hash. It never incorporates wall clock, process ID, random UUIDs, machine identity, database identity, or local path. + +Each event result extends a canonical authority chain over the prior chain hash, event hash, synthetic-account snapshot hash, exact canonical shadow decision hash, exact canonical shadow summary hash, blocker set, durable provenance hash, and safety-facts hash. Segment results bind their first/final authority-chain hashes. The final campaign result binds the ordered segment results, reporting-window hashes, and final canonical decision-chain hash. A separate lineage-free policy projection and policy chain support point-in-time mutation comparisons; they never replace the canonical authority chain in a campaign result. + +Mutations preserve event-data, semantic decision, and lineage-free policy-chain values before the mutation point and change the policy chain from the mutation point forward. Mutation records preserve the old canonical evidence and bind the parent full campaign hash, the parent and mutated policy-chain hashes, and a separate lineage hash; they do not claim or persist a child canonical campaign/spec authority. Reusing one run ID with a different payload is a conflict, never an overwrite. + +## CLI + +Install the `open-core` package to expose `secure-eval-live-shadow-campaign`. + +### Plan + +```text +secure-eval-live-shadow-campaign plan \ + --campaign full-90d \ + --repository-sha +``` + +`plan` reports the campaign/spec/generator identities, event and segment counts, expected deterministic run IDs, no-write facts, and PostgreSQL target rules. It does not connect to a database or open a socket. + +### Run + +```text +secure-eval-live-shadow-campaign run \ + --campaign full-90d \ + --postgres-host 127.0.0.1 \ + --postgres-database secure_eval_phase8b_shadow_campaign_ +``` + +Persistent operations require PostgreSQL 16, a literal host of `127.0.0.1` or `::1`, and a database matching exactly `secure_eval_phase8b_shadow_campaign_`. A generic shadow, operator, default, remote, mixed-case, or otherwise unrelated database is rejected before connection. The CLI has no password argument; authentication is delegated to libpq configuration. + +For persistent commands, an optional supplied repository SHA is only a confirmation: it must exactly equal the repository identity resolved from the running code before database connection or event generation. It cannot override runtime identity. `plan` remains the connection-free command that requires an explicit reviewed SHA for deterministic planning. + +### Resume, inspect, and verify + +```text +secure-eval-live-shadow-campaign resume \ + --campaign full-90d \ + --postgres-host 127.0.0.1 \ + --postgres-database secure_eval_phase8b_shadow_campaign_ + +secure-eval-live-shadow-campaign inspect \ + --campaign full-90d \ + --postgres-host 127.0.0.1 \ + --postgres-database secure_eval_phase8b_shadow_campaign_ + +secure-eval-live-shadow-campaign verify \ + --campaign full-90d \ + --postgres-host 127.0.0.1 \ + --postgres-database secure_eval_phase8b_shadow_campaign_ +``` + +`resume` derives the completed prefix from canonical validated bundles and resumes from the earliest missing event. It does not trust a caller-provided checkpoint, skip an earlier gap because a later row exists, duplicate authoritative rows, or repair corrupted rows. + +`inspect` reports public-safe campaign identity, completed and missing counts, first missing index, validated/invalid bundle counts, segment progress, blocker frequencies, decision-chain hash, and zero-write facts. + +`verify` regenerates the corpus and rechecks expected run IDs, stored rows, event/segment/window/campaign hashes, chain reconstruction, safety facts, and no-write facts. + +All command responses use a recursively fixed JSON schema, not only a top-level key allowlist. Object key order, array shape, exact primitive type, status and blocker vocabulary, canonical lowercase hash and UUID forms, count relationships, the twelve ordered segment IDs with exact `accepted`/`blocked` children, and the ordered `1d`, `7d`, `30d`, and `90d` hash prefix are validated. Python booleans are not accepted as integers, and benign-looking extra nested keys fail closed. `plan` contains exactly the ordered 26 boundary/sample run IDs derived from the twelve segment starts, twelve segment ends, and fixed reporting boundaries. + +Structural schema validation runs before the recursive privacy scan and JSON serialization. The privacy scan therefore operates only on an already classified shape; it remains an additional content control for forbidden paths, connection strings, credentials, account data, and private strategy text rather than a permissive fallback. + +Failure responses have one reviewed command/stage/blocker combination and preserve truthful operation-local processed-event, newly committed-row, repository-derived first-gap, and safety counts. A committed but invalid canonical row is reported at its public event index when that index can be safely recovered. Responses exclude raw exceptions, stack traces, paths, DSNs, usernames, passwords, environment dumps, raw market rows, detailed balances, and private strategy information. + +## Persistence, replay, and resume + +PostgreSQL remains the only authoritative persistent target. Each campaign event is stored as the existing canonical Phase 8B shadow bundle in `audit.run_manifests` and is validated with the accepted bundle and manifest-row validator on write, load, replay, inspect, and verification. + +There is intentionally no campaign aggregate table and no `campaign_complete = true` authority. Completion is reconstructed from the deterministic expected event set, exact event/run identities, canonical validated bundles, and a complete hash chain. Exact replay adds no row and reproduces every decision, segment, window, and campaign hash. A corrupt committed row fails closed and is not automatically repaired. + +This is also the schema limitation: migrations `0001` through `0026` do not contain a campaign-level persistence model. The implementation therefore uses one validated existing bundle per event and reconstructs aggregate state. No migration is modified, migration count remains 26, migration `0026` retains SHA-256 `698772fb68c5c4981256682d064c3be641193ab10c8dbf55e1a5b390ca7c504a`, and migration `0027` does not exist. + +Fresh-process resume coverage includes interruptions after events 1, 17, 288, 2,016, 4,320, 8,640, 12,960, 17,280, 21,600, and 25,919, plus beginning, middle, multiple, and cross-segment sparse gaps. + +## Verification matrices and metrics + +The executable assurance surface contains the exact ten restart checkpoints plus four sparse-gap cases; eight mutation cases for price, timestamp, spread, instrument metadata, synthetic balance, kill switch, pending order, and daily-loss state; six anti-lookahead cases; all ten required concurrency semantics; all nine shared shadow-runtime crash points; and all ten campaign lifecycle crash points. Case identities, ordered catalogs, executed checks, and result hashes are part of the verifier result rather than caller-supplied pass counters. + +The campaign result, inspection, verifier, and evidence views collectively report: + +- total, processed, committed, completed, persisted, replayed, accepted, blocked, and hypothetical-intent counts; +- blocker frequencies and exact per-segment accepted/blocked counts; +- segment, 1-day, 7-day, 30-day, 90-day, event-sequence, decision-chain, and campaign hashes; +- restart, replay, mutation, anti-lookahead, concurrency, crash, and corrupted-bundle verification outcomes; +- network read/write, production transport, authenticated endpoint, credential read, production write, submit/cancel reachability, real-account use, and operator-database access facts. + +No duration observation enters a deterministic hash or constitutes a throughput promise. Public evidence omits P&L, Sharpe ratio, CAGR, expected return, strategy edge, private alpha parameters, and account details. + +## Executable verifier and public evidence + +The executable verifier is `phase8b-historical-shadow-campaign-verifier-v1`. It executes the campaign, replay, restart, mutation, anti-lookahead, concurrency, and crash logic itself; it does not accept caller-supplied passed counts. Its result binds repository and implementation identities, corpus/account hashes, event and segment counts, sequence/segment/window/chain/campaign hashes, individual case results, zero-write facts, and PostgreSQL classification. + +The public candidate artifact is: + +`docs/evidence/phase8b_historical_shadow_campaign_public.json` + +Its keys and order are fixed, and its self-hash binds every other field. Validation reruns the verifier and compares the complete result. Three consecutive generations for one repository SHA must be byte-identical. Validation also scans exact keys/order, forbidden keys, secret patterns, local paths, account data, private strategy data, and unclassified high-entropy values. + +The checked artifact remains `implemented_pending_independent_audit`. Artifact generation performs no real public-network smoke, operator bootstrap, authenticated proof, credential read, or production write. Unless artifact generation itself ran a real disposable PostgreSQL campaign, it must report `POSTGRESQL_CAMPAIGN_NOT_EXECUTED_DURING_ARTIFACT_GENERATION` and zero PostgreSQL campaign events; a separate CI result cannot be backfilled into the artifact. + +## Verification profiles and CI + +The existing six-job GitHub Actions shape is preserved: + +- Ubuntu Python 3.11, Ubuntu Python 3.13, and Windows Python 3.12 run the exact 1,440-event compact profile; +- Ubuntu Python 3.12 runs the full 25,920-event offline verifier, all focused campaign suites, and evidence reproducibility; +- PostgreSQL 16 runs at least 2,016 persisted events with replay, restart, sparse gaps, conflict, corruption, concurrent run/resume, campaign-level crash recovery, and canonical validation; +- the public/private runtime-boundary job checks that campaign imports and injected dependencies cannot reach credentials, authenticated endpoints, operator databases, arbitrary transports, production brokers, submit/cancel, account powers, derivatives, or production FIX. + +These are configured validation obligations, not a statement that branch-head, merge-ref, PostgreSQL, or independent-audit checks have already passed. CI is offline and may not use exchange access or GitHub secrets. + +## Independent-audit checkpoint + +Independent review must verify the implementation, deterministic hashes, checked public evidence, migration zero-diff, exact event thresholds, canonical committed rows, anti-lookahead behavior, restart/replay/gap behavior, mutation lineage, concurrency/crash classifications, and the no-authority import graph before the candidate can be accepted. + +Until that review is complete, the only truthful status is implementation candidate pending independent audit. diff --git a/open-core/pyproject.toml b/open-core/pyproject.toml index 602b293..b8e9e49 100644 --- a/open-core/pyproject.toml +++ b/open-core/pyproject.toml @@ -32,6 +32,7 @@ secure-eval-live-reconcile = "secure_eval_wrapper.live.cli:reconcile_main" secure-eval-live-kill = "secure_eval_wrapper.live.cli:kill_main" secure-eval-live-bootstrap = "secure_eval_wrapper.live.bootstrap_cli:main" secure-eval-live-shadow = "secure_eval_wrapper.live.shadow_cli:main" +secure-eval-live-shadow-campaign = "secure_eval_wrapper.live.shadow_campaign_cli:main" [tool.setuptools] package-dir = {"" = "src"} diff --git a/open-core/scripts/generate_phase8b_historical_shadow_campaign_evidence.py b/open-core/scripts/generate_phase8b_historical_shadow_campaign_evidence.py new file mode 100644 index 0000000..609c227 --- /dev/null +++ b/open-core/scripts/generate_phase8b_historical_shadow_campaign_evidence.py @@ -0,0 +1,134 @@ +"""Generate the fixed-allowlist Phase 8B historical shadow campaign artifact.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from secure_eval_wrapper.live.shadow_campaign_evidence import ( + build_public_historical_shadow_campaign_evidence, + canonical_public_historical_shadow_campaign_evidence_bytes, +) + + +DEFAULT_OUTPUT = "docs/evidence/phase8b_historical_shadow_campaign_public.json" + +_SAFETY_OUTPUT_KEYS = ( + "network_read_count", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + "production_submit_reachable", + "production_cancel_reachable", + "real_account_data_used", + "operator_database_accessed", +) +_GENERATOR_OUTPUT_KEY_ALLOWLISTS = frozenset({ + ( + "operation", "status", "evidence_payload_sha256", + "processed_event_count", "committed_row_count", "first_missing_index", + "postgresql_campaign_event_count", "output_classification", + ) + _SAFETY_OUTPUT_KEYS, + ( + "operation", "status", "failure_stage", "blockers", + "processed_event_count", "committed_row_count", "first_missing_index", + "postgresql_campaign_event_count", "output_classification", + ) + _SAFETY_OUTPUT_KEYS, +}) + + +def canonical_evidence_bytes(payload: object) -> bytes: + return canonical_public_historical_shadow_campaign_evidence_bytes(payload) + + +class _UsageError(ValueError): + pass + + +class _SafeArgumentParser(argparse.ArgumentParser): + def error(self, message: str) -> None: # pragma: no cover - argparse dispatch + raise _UsageError("invalid evidence generator arguments") + + +def _emit(payload: dict[str, object]) -> None: + if tuple(payload) not in _GENERATOR_OUTPUT_KEY_ALLOWLISTS: + raise ValueError("evidence generator output keys differ from its fixed allowlist") + print(json.dumps(payload, ensure_ascii=True, separators=(",", ":"))) + + +def _safety_fields() -> dict[str, object]: + return { + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + } + + +def main(argv=None) -> int: + processed_event_count = 0 + first_missing_index: int | None = 0 + try: + parser = _SafeArgumentParser(add_help=False) + parser.add_argument("--repository-sha", required=True) + parser.add_argument("--output", default=DEFAULT_OUTPUT) + args = parser.parse_args(argv) + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=args.repository_sha + ) + processed_event_count = payload["event_count"] + if isinstance(processed_event_count, bool) or not isinstance(processed_event_count, int): + raise ValueError("evidence event count is not an integer") + first_missing_index = None + encoded = canonical_evidence_bytes(payload) + # Build three independent verifier-derived objects and require byte identity. + for _ in range(2): + repeated = build_public_historical_shadow_campaign_evidence( + repository_sha=args.repository_sha + ) + if canonical_evidence_bytes(repeated) != encoded: + raise AssertionError("campaign evidence generation is not byte-identical") + path = Path(args.output) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(encoded) + _emit({ + "operation": "phase8b_historical_shadow_campaign_evidence_generation", + "status": "complete", + "evidence_payload_sha256": payload["evidence_payload_sha256"], + "processed_event_count": payload["event_count"], + "committed_row_count": 0, + "first_missing_index": None, + "postgresql_campaign_event_count": payload["postgresql_campaign_event_count"], + "output_classification": "public_evidence_file", + **_safety_fields(), + }) + return 0 + except Exception: + _emit({ + "operation": "phase8b_historical_shadow_campaign_evidence_generation", + "status": "blocked", + "failure_stage": "verification_or_artifact_write", + "blockers": ["campaign_evidence_generation_failed_closed"], + "processed_event_count": processed_event_count, + "committed_row_count": 0, + "first_missing_index": first_missing_index, + "postgresql_campaign_event_count": 0, + "output_classification": "no_artifact_authority", + **_safety_fields(), + }) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) + + +__all__ = ["DEFAULT_OUTPUT", "canonical_evidence_bytes", "main"] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_accounts.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_accounts.py new file mode 100644 index 0000000..a8664a2 --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_accounts.py @@ -0,0 +1,387 @@ +"""Deterministic synthetic account timeline for the historical shadow campaign.""" + +from __future__ import annotations + +from decimal import Decimal +from typing import Mapping + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + _validate_campaign_spec_authority, +) +from .shadow_campaign_models import ( + ShadowCampaignSegment, + ShadowCampaignSpec, + SyntheticAccountTimelineSnapshot, + _is_sealed_canonical_mapping, + _validate_synthetic_account_snapshot_authority, + synthetic_timeline_snapshot_id, +) + + +SHADOW_CAMPAIGN_ACCOUNT_TIMELINE_VERSION = ( + "phase8b-historical-shadow-synthetic-account-timeline-v1" +) + +SYNTHETIC_ACCOUNT_TIMELINE_STATES = ( + "clean_flat", + "near_maximum_order_notional", + "low_quote_balance", + "low_base_balance", + "existing_spot_inventory", + "pending_buy_pressure", + "pending_sell_pressure", + "reserved_notional_pressure", + "daily_loss_guard_near_threshold", + "daily_loss_guard_breached", + "kill_switch_active", + "recovery_to_clean_synthetic_state", +) + +_RISK_LIMITS = { + "profile": "guarded_live_shared", + "maximum_order_notional": Decimal("1000"), + "maximum_gross_exposure": Decimal("5000"), + "maximum_daily_realized_loss": Decimal("500"), +} + + +def _balance( + asset: str, + total: str, + available: str, + reserved: str, +) -> Mapping[str, object]: + return { + "asset": asset, + "total": Decimal(total), + "available": Decimal(available), + "reserved": Decimal(reserved), + } + + +def _account_payload( + *, + quote_total: str = "10000", + quote_available: str = "10000", + quote_reserved: str = "0", + base_total: str = "1", + base_available: str = "1", + base_reserved: str = "0", + positions: tuple[Mapping[str, object], ...] = (), + pending_orders: tuple[Mapping[str, object], ...] = (), + reserved_notional: str = "0", + daily_realized_pnl: str = "0", + current_equity: str = "10000", + high_watermark_equity: str = "10000", + kill_switch_active: bool = False, + requested_order_notional: str = "500", + requested_side: str = "buy", +) -> Mapping[str, object]: + """Build one public-safe account input without expected-result metadata.""" + + return { + "synthetic_account": True, + "account_classification": "synthetic_spot", + "balances": ( + _balance("USDT", quote_total, quote_available, quote_reserved), + _balance("BTC", base_total, base_available, base_reserved), + ), + "positions": positions, + "pending_orders": pending_orders, + "reserved_notional": Decimal(reserved_notional), + "permissions": ("shadow_decide", "synthetic_trade_profile"), + "daily_realized_pnl": Decimal(daily_realized_pnl), + "current_equity": Decimal(current_equity), + "high_watermark_equity": Decimal(high_watermark_equity), + "kill_switch_active": kill_switch_active, + "risk_limits": _RISK_LIMITS, + "campaign_decision_context": { + "requested_order_notional": Decimal(requested_order_notional), + "requested_side": requested_side, + }, + } + + +def _position(quantity: str, notional: str) -> Mapping[str, object]: + return { + "instrument": "BTC-USDT", + "instrument_type": "spot", + "quantity": Decimal(quantity), + "notional": Decimal(notional), + "settlement_asset": "USDT", + } + + +def _pending_order( + side: str, + quantity: str, + reserved_notional: str, +) -> Mapping[str, object]: + return { + "instrument": "BTC-USDT", + "side": side, + "quantity": Decimal(quantity), + "reserved_notional": Decimal(reserved_notional), + } + + +def _state_payloads() -> tuple[tuple[str, Mapping[str, object], tuple[str, ...]], ...]: + return ( + ("clean_flat", _account_payload(), ()), + ( + "near_maximum_order_notional", + _account_payload(requested_order_notional="999.9"), + (), + ), + ( + "low_quote_balance", + _account_payload( + quote_total="25", + quote_available="25", + requested_order_notional="500", + requested_side="buy", + ), + ("insufficient_quote_balance",), + ), + ( + "low_base_balance", + _account_payload( + base_total="0", + base_available="0", + requested_order_notional="500", + requested_side="sell", + ), + ("insufficient_base_balance",), + ), + ( + "existing_spot_inventory", + _account_payload( + base_total="1.25", + base_available="1.25", + positions=(_position("0.25", "12500"),), + requested_side="sell", + ), + (), + ), + ( + "pending_buy_pressure", + _account_payload( + quote_available="9200", + quote_reserved="800", + pending_orders=(_pending_order("buy", "0.016", "800"),), + reserved_notional="800", + ), + (), + ), + ( + "pending_sell_pressure", + _account_payload( + base_available="0.85", + base_reserved="0.15", + pending_orders=(_pending_order("sell", "0.15", "7500"),), + requested_side="sell", + ), + (), + ), + ( + "reserved_notional_pressure", + _account_payload( + quote_available="500", + quote_reserved="9500", + reserved_notional="9500", + ), + ("excessive_reserved_notional",), + ), + ( + "daily_loss_guard_near_threshold", + _account_payload( + daily_realized_pnl="-490", + current_equity="9510", + high_watermark_equity="10000", + ), + (), + ), + ( + "daily_loss_guard_breached", + _account_payload( + daily_realized_pnl="-510", + current_equity="9490", + high_watermark_equity="10000", + ), + ("maximum_daily_realized_loss",), + ), + ( + "kill_switch_active", + _account_payload(kill_switch_active=True), + ("kill_switch_not_armed",), + ), + ( + "recovery_to_clean_synthetic_state", + _account_payload(), + (), + ), + ) + + +def _exact_spec_segments( + spec: ShadowCampaignSpec, +) -> tuple[ShadowCampaignSegment, ...]: + """Reject injected spec authority before reading campaign properties.""" + + return _validate_campaign_spec_authority(spec) + + +def _exact_synthetic_account_timeline( + timeline: tuple[SyntheticAccountTimelineSnapshot, ...], +) -> tuple[SyntheticAccountTimelineSnapshot, ...]: + """Validate the outer tuple and every item before reading snapshot fields.""" + + if type(timeline) is not tuple: + raise TypeError("timeline must be an exact tuple") + if not timeline: + raise ValueError("timeline must contain synthetic timeline snapshots") + if any(type(item) is not SyntheticAccountTimelineSnapshot for item in timeline): + raise TypeError( + "timeline must contain exact SyntheticAccountTimelineSnapshot values" + ) + for item in timeline: + _validate_synthetic_account_snapshot_authority(item) + return timeline + + +def full_90d_synthetic_account_timeline( + spec: ShadowCampaignSpec = FULL_90D_CAMPAIGN_SPEC, +) -> tuple[SyntheticAccountTimelineSnapshot, ...]: + """Build 12 pre-determined synthetic snapshots, one per fixed segment.""" + + segments = _exact_spec_segments(spec) + definitions = _state_payloads() + if len(segments) != len(definitions): + raise ValueError("the full synthetic timeline requires exactly 12 segments") + snapshots: list[SyntheticAccountTimelineSnapshot] = [] + for segment, (state_name, runtime_payload, blockers) in zip( + segments, definitions + ): + snapshot_id = synthetic_timeline_snapshot_id( + state_name=state_name, + effective_start_event_index=segment.start_event_index, + effective_end_event_index=segment.end_event_index_exclusive, + runtime_payload=runtime_payload, + ) + snapshots.append( + SyntheticAccountTimelineSnapshot( + snapshot_id=snapshot_id, + state_name=state_name, + effective_start_event_index=segment.start_event_index, + effective_end_event_index=segment.end_event_index_exclusive, + runtime_payload=runtime_payload, + expected_account_blockers=blockers, + ) + ) + return tuple(snapshots) + + +FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE = full_90d_synthetic_account_timeline() + + +def synthetic_account_timeline_sha256( + timeline: tuple[SyntheticAccountTimelineSnapshot, ...] | None = None, +) -> str: + """Hash ordered runtime snapshots and separately classified expectations.""" + + if timeline is None: + timeline = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + values = _exact_synthetic_account_timeline(timeline) + expected_start = 0 + for item in values: + if item.effective_start_event_index != expected_start: + raise ValueError("synthetic timeline ranges must be contiguous") + expected_start = item.effective_end_event_index + return sha256_payload( + { + "timeline_version": SHADOW_CAMPAIGN_ACCOUNT_TIMELINE_VERSION, + "event_count": expected_start, + "entries": tuple(item.timeline_entry_sha256 for item in values), + } + ) + + +FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256 = synthetic_account_timeline_sha256( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE +) +SYNTHETIC_ACCOUNT_TIMELINE_HASH = ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256 +) + + +def synthetic_account_snapshot_for_event( + event_index: int, + timeline: tuple[SyntheticAccountTimelineSnapshot, ...] | None = None, +) -> SyntheticAccountTimelineSnapshot: + """Return the one pre-determined risk input effective for an event index.""" + + if type(event_index) is not int or event_index < 0: + raise ValueError("event_index must be a non-negative integer") + if timeline is None: + timeline = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + values = _exact_synthetic_account_timeline(timeline) + for snapshot in values: + if ( + snapshot.effective_start_event_index + <= event_index + < snapshot.effective_end_event_index + ): + return snapshot + raise IndexError("event_index is outside the synthetic account timeline") + + +def scenario_account_payload( + snapshot: SyntheticAccountTimelineSnapshot, +) -> dict[str, object]: + """Return a detached list/dict projection for the existing fixture runtime parser.""" + + if type(snapshot) is not SyntheticAccountTimelineSnapshot: + raise TypeError( + "snapshot must be the exact SyntheticAccountTimelineSnapshot type" + ) + _validate_synthetic_account_snapshot_authority(snapshot) + runtime_payload = object.__getattribute__(snapshot, "runtime_payload") + + def thaw(value: object) -> object: + if _is_sealed_canonical_mapping(value): + result: dict[str, object] = {} + # A sealed mapping is an exact, module-owned mappingproxy; only + # those values may be traversed at this projection boundary. + for key, item in value.items(): + if type(key) is not str: + raise TypeError("snapshot runtime payload keys must be exact strings") + result[key] = thaw(item) + return result + if type(value) is tuple: + return [thaw(item) for item in value] + if value is None or type(value) in (str, bool, int, Decimal): + return value + raise TypeError( + "snapshot runtime payload contains an unsupported injected value" + ) + + result = thaw(runtime_payload) + if type(result) is not dict: + raise AssertionError("synthetic runtime payload projection is not a mapping") + return result + + +__all__ = [ + "FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE", + "FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256", + "SHADOW_CAMPAIGN_ACCOUNT_TIMELINE_VERSION", + "SYNTHETIC_ACCOUNT_TIMELINE_HASH", + "SYNTHETIC_ACCOUNT_TIMELINE_STATES", + "full_90d_synthetic_account_timeline", + "scenario_account_payload", + "synthetic_account_snapshot_for_event", + "synthetic_account_timeline_sha256", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_cli.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_cli.py new file mode 100644 index 0000000..937e1a0 --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_cli.py @@ -0,0 +1,1159 @@ +"""Public-safe CLI for the deterministic Phase 8B historical shadow campaign.""" +from __future__ import annotations + +import argparse +import json +import re +import sys +from collections.abc import Mapping, Sequence +from uuid import UUID + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .identity import resolve_runtime_repository_identity, validate_git_commit_sha +from .shadow_campaign_accounts import synthetic_account_snapshot_for_event +from .shadow_campaign_corpus import FULL_90D_CAMPAIGN_SPEC, iter_shadow_campaign_events +from .shadow_campaign_runtime import ( + _copy_shadow_campaign_failure_progress, + ShadowCampaignProgress, + ShadowCampaignOperationFailure, + ShadowCampaignResult, + ShadowCampaignSpecConflict, + ShadowCampaignRuntime, + ShadowCampaignInjectedCrash, + derive_shadow_campaign_run_id, +) +from .shadow_models import ShadowSafetyFacts +from .shadow_repository import ( + PostgresShadowRepository, + ShadowInjectedCrash, + ShadowCampaignRepositoryCorruption, + validate_shadow_campaign_database_name, + validate_shadow_postgres_host, +) + + +_OPERATION = "phase8b_historical_shadow_campaign" +_CAMPAIGN_NAME = "full-90d" +_COMMANDS = ("plan", "run", "resume", "inspect", "verify") +_PLAN_SAMPLE_INDEXES = tuple(sorted( + {0, 287, 2_015, 8_639, 25_919} + | {segment.start_event_index for segment in FULL_90D_CAMPAIGN_SPEC.segments} + | { + segment.end_event_index_exclusive - 1 + for segment in FULL_90D_CAMPAIGN_SPEC.segments + } +)) +_CANONICAL_SEGMENT_IDS = tuple( + segment.segment_id for segment in FULL_90D_CAMPAIGN_SPEC.segments +) +_CANONICAL_SEGMENT_SIZES = tuple( + segment.event_count for segment in FULL_90D_CAMPAIGN_SPEC.segments +) +_CANONICAL_WINDOWS = tuple(FULL_90D_CAMPAIGN_SPEC.windows.items()) + +# Reviewed outputs of the shared public/synthetic validation and guarded-live +# policy path. CLI data may contain a subset; arbitrary vocabulary is rejected. +_REVIEWED_POLICY_BLOCKERS = frozenset( + { + "approval_notional", "ask_must_be_positive", "bid_must_be_positive", + "conflicting_account_classification", "conflicting_public_sources", + "crossed_bid_ask", "duplicate_public_response_rows", + "duplicate_synthetic_position", "excessive_reserved_notional", + "fixture_classification_mismatch", "fixture_market_data_forbidden", + "incomplete_public_response", "insufficient_base_balance", + "insufficient_quote_balance", "instrument_delisted", + "instrument_not_allowed", "instrument_not_live", "kill_switch_not_armed", + "malformed_account_snapshot", "malformed_public_response", + "market_data_currency_mismatch", "market_data_future", + "market_data_identity_mismatch", "market_data_instrument_mismatch", + "market_data_invalid", "market_data_non_final", "market_data_price_missing", + "market_data_provider_mismatch", "market_data_quarantined", + "market_data_series_fields_mismatch", "market_data_stale", + "market_price_must_be_positive", "market_price_not_finite", + "market_snapshot_unavailable", "maximum_cancellations_per_minute", + "maximum_clock_skew", "maximum_daily_realized_loss", + "maximum_daily_submitted_notional", "maximum_drawdown", + "maximum_gross_exposure", "maximum_net_exposure", + "maximum_open_order_count", "maximum_order_notional", + "maximum_orders_per_minute", "maximum_position_notional_or_spot_short", + "maximum_reference_price_deviation", "maximum_run_duration", + "maximum_transport_failures", "missing_instrument_metadata", + "negative_synthetic_balance", "only_limit_orders_allowed", + "operational_classification_mismatch", "partial_public_response", + "public_market_future_timestamp", "public_network_connection_failure", + "public_network_rate_limit", "public_network_timeout", + "public_provider_error", "public_response_replay", + "quantity_above_maximum_after_rounding", + "quantity_below_minimum_after_rounding", "quantity_must_be_positive", + "quantity_not_finite", "reconciliation_blocked", "stale_account_snapshot", + "stale_cached_response", "stale_market_data", "stale_reconciliation", + "synthetic_derivative_exposure", "synthetic_permission_not_trade_enabled", + "synthetic_short_position", "unacknowledged_order_age", + "unknown_order_age", "wrong_instrument_type", "wrong_settlement_asset", + } +) +_OPERATION_FAILURE_STAGES = ( + "repository_validation", + "event_execution", + "final_repository_validation", +) +_FAILURE_COMBINATIONS = frozenset( + { + ("unknown", "argument_validation", "invalid_campaign_arguments"), + ("plan", "planning", "campaign_plan_failed_closed"), + ("unknown", "serialization", "campaign_result_serialization_failed"), + *(( + command, + "target_or_runtime", + "campaign_operation_failed_closed", + ) for command in _COMMANDS[1:]), + *(( + command, + "campaign_runtime", + "campaign_runtime_interrupted", + ) for command in _COMMANDS[1:]), + *(( + command, + stage, + "campaign_operation_failed_closed", + ) for command in _COMMANDS[1:] for stage in _OPERATION_FAILURE_STAGES), + *(( + command, + stage, + "campaign_authority_conflict", + ) for command in _COMMANDS[1:] for stage in ( + "event_execution", "final_repository_validation", + )), + } +) + +_SAFETY_OUTPUT_KEYS = ( + "network_read_count", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + "production_submit_reachable", + "production_cancel_reachable", + "real_account_data_used", + "operator_database_accessed", +) +_CLI_PUBLIC_OUTPUT_KEY_ALLOWLISTS = frozenset( + { + ( + "operation", "command", "status", "commands", "campaigns", + "password_argument_supported", + ) + _SAFETY_OUTPUT_KEYS, + ( + "operation", "command", "status", "campaign_id", + "processed_event_count", "committed_row_count", "first_missing_index", + "failure_stage", "blockers", + ) + _SAFETY_OUTPUT_KEYS, + ( + "operation", "command", "status", "campaign_id", "repository_sha", + "campaign_spec_sha256", "corpus_generator_sha256", "event_count", + "segment_count", "timeframe", "expected_run_id_count", + "expected_run_id_set_sha256", "expected_deterministic_run_ids", + "postgresql_version_required", "postgresql_host_rule", + "postgresql_database_rule", "database_connection_attempted", + "socket_opened", + ) + _SAFETY_OUTPUT_KEYS, + ( + "operation", "command", "status", "campaign_id", + "campaign_spec_sha256", "repository_sha", "total_event_count", + "processed_event_count", "committed_row_count", + "completed_event_count", "missing_event_count", "first_missing_index", + "accepted_decision_count", "blocked_decision_count", + "shadow_intent_count", "blocker_frequencies", "segment_decision_counts", + "segment_result_hashes", "window_result_hashes", + "event_sequence_sha256", "final_decision_chain_sha256", + "campaign_result_sha256", "persisted_count", "replay_count", + ) + _SAFETY_OUTPUT_KEYS, + ( + "operation", "command", "status", "campaign_id", + "campaign_spec_sha256", "repository_sha", "total_event_count", + "completed_event_count", "missing_event_count", "valid_bundle_count", + "invalid_bundle_count", "first_missing_index", "segment_completion", + "accepted_decision_count", "blocked_decision_count", + "shadow_intent_count", "blocker_frequencies", "segment_decision_counts", + "segment_result_hashes", "window_result_hashes", + "event_sequence_sha256", "final_decision_chain_sha256", + "campaign_result_sha256", + ) + _SAFETY_OUTPUT_KEYS, + } +) +_UNSAFE_PUBLIC_TEXT = re.compile( + r"(?:[A-Za-z]:[\\/]|\\\\[^\\]+[\\/]|" + r"/(?:home|users|tmp|root|workspace|etc)(?:/|$)|/var/private(?:/|$)|" + r"(?:postgres(?:ql)?|https?)://|" + r"(?:authorization|password|passphrase|api[_ -]?key|bearer)[ \\t]*[:=])", + re.I, +) +_UNSAFE_NESTED_KEY = re.compile( + r"(?:user(?:name)?|dsn|secret|passphrase|api_?key|raw_?(?:market|account|trade)|" + r"private_?(?:strategy|alpha)|actual_?(?:balance|position|order))", + re.I, +) + + +def _mapping( + value: object, + keys: tuple[str, ...], + name: str, +) -> dict[str, object]: + if type(value) is not dict or tuple(value) != keys: + raise ValueError(f"{name} differs from the fixed object schema") + return value + + +def _array(value: object, name: str) -> list[object]: + if type(value) is not list: + raise ValueError(f"{name} must be an exact JSON array") + return value + + +def _integer( + value: object, + name: str, + *, + maximum: int | None = None, + positive: bool = False, +) -> int: + if type(value) is not int or value < (1 if positive else 0): + raise ValueError(f"{name} must be an exact non-negative integer") + if maximum is not None and value > maximum: + raise ValueError(f"{name} exceeds its fixed maximum") + return value + + +def _text(value: object, name: str) -> str: + if type(value) is not str or not value: + raise ValueError(f"{name} must be non-empty text") + return value + + +def _boolean(value: object, name: str) -> bool: + if type(value) is not bool: + raise ValueError(f"{name} must be an exact boolean") + return value + + +def _digest(value: object, name: str) -> str: + text_value = _text(value, name) + if re.fullmatch(r"[0-9a-f]{64}", text_value) is None: + raise ValueError(f"{name} must be a lowercase SHA-256 digest") + return text_value + + +def _git_sha(value: object, name: str) -> str: + text_value = _text(value, name) + if re.fullmatch(r"[0-9a-f]{40}", text_value) is None: + raise ValueError(f"{name} must be a lowercase full Git commit SHA") + return text_value + + +def _uuid(value: object, name: str) -> str: + text_value = _text(value, name) + try: + parsed = UUID(text_value) + except (ValueError, AttributeError) as exc: + raise ValueError(f"{name} must be a canonical UUID") from exc + if str(parsed) != text_value: + raise ValueError(f"{name} must be a canonical UUID") + return text_value + + +def _exact(value: object, expected: object, name: str) -> None: + if type(value) is not type(expected) or value != expected: + raise ValueError(f"{name} differs from its fixed value") + + +def _safety(payload: Mapping[str, object], *, require_zero: bool) -> None: + counters = tuple( + _integer(payload[key], key) for key in _SAFETY_OUTPUT_KEYS[:6] + ) + for key in _SAFETY_OUTPUT_KEYS[6:]: + if _boolean(payload[key], key) is not False: + raise ValueError(f"{key} must remain false") + if require_zero and any(counters): + raise ValueError("historical campaign success must have zero safety counts") + + +def _blocker_frequencies(value: object, blocked_count: int) -> None: + if type(value) is not dict: + raise ValueError("blocker_frequencies must be an exact JSON object") + keys = tuple(value) + if keys != tuple(sorted(keys)): + raise ValueError("blocker_frequencies must use sorted key order") + total = 0 + for key, count in value.items(): + if type(key) is not str or key not in _REVIEWED_POLICY_BLOCKERS: + raise ValueError("blocker_frequencies contains unreviewed vocabulary") + total += _integer(count, f"blocker_frequencies.{key}", positive=True) + if (blocked_count == 0) != (total == 0): + raise ValueError("blocker_frequencies disagree with blocked decisions") + if blocked_count and total < blocked_count: + raise ValueError("blocker_frequencies undercount blocked decisions") + + +def _segment_counts( + value: object, + *, + accepted_count: int, + blocked_count: int, + expected_segment_totals: tuple[int, ...], +) -> None: + mapping = _mapping(value, _CANONICAL_SEGMENT_IDS, "segment_decision_counts") + accepted_total = 0 + blocked_total = 0 + for segment_id, maximum, expected_total in zip( + _CANONICAL_SEGMENT_IDS, + _CANONICAL_SEGMENT_SIZES, + expected_segment_totals, + ): + counts = _mapping( + mapping[segment_id], ("accepted", "blocked"), + f"segment_decision_counts.{segment_id}", + ) + accepted = _integer(counts["accepted"], "segment accepted", maximum=maximum) + blocked = _integer(counts["blocked"], "segment blocked", maximum=maximum) + if accepted + blocked != expected_total: + raise ValueError("segment decision counts disagree with segment completion") + accepted_total += accepted + blocked_total += blocked + if accepted_total != accepted_count or blocked_total != blocked_count: + raise ValueError("segment decision counts disagree with campaign totals") + + +def _hash_prefix( + value: object, + canonical_keys: tuple[str, ...], + expected_length: int, + name: str, +) -> None: + if type(value) is not dict: + raise ValueError(f"{name} must be an exact JSON object") + if tuple(value) != canonical_keys[:expected_length]: + raise ValueError(f"{name} must be the canonical completed prefix") + for key, digest in value.items(): + _digest(digest, f"{name}.{key}") + + +def _prefix_lengths(first_missing_index: int | None) -> tuple[int, int]: + prefix_count = ( + FULL_90D_CAMPAIGN_SPEC.event_count + if first_missing_index is None + else first_missing_index + ) + segment_length = sum( + segment.end_event_index_exclusive <= prefix_count + for segment in FULL_90D_CAMPAIGN_SPEC.segments + ) + window_length = sum(count <= prefix_count for _, count in _CANONICAL_WINDOWS) + return segment_length, window_length + + +def _validate_help(payload: dict[str, object]) -> None: + _exact(payload["operation"], _OPERATION, "operation") + _exact(payload["command"], "help", "command") + _exact(payload["status"], "available", "status") + commands = _array(payload["commands"], "commands") + campaigns = _array(payload["campaigns"], "campaigns") + if tuple(commands) != _COMMANDS or campaigns != [_CAMPAIGN_NAME]: + raise ValueError("help vocabulary differs from the fixed interface") + if _boolean(payload["password_argument_supported"], "password support") is not False: + raise ValueError("password arguments must remain unsupported") + _safety(payload, require_zero=True) + + +def _validate_failure(payload: dict[str, object]) -> None: + _exact(payload["operation"], _OPERATION, "operation") + command = _text(payload["command"], "command") + _exact(payload["status"], "blocked", "status") + _exact( + payload["campaign_id"], FULL_90D_CAMPAIGN_SPEC.campaign_id, + "campaign_id", + ) + _integer( + payload["processed_event_count"], + "processed_event_count", + maximum=FULL_90D_CAMPAIGN_SPEC.event_count, + ) + _integer( + payload["committed_row_count"], + "committed_row_count", + maximum=FULL_90D_CAMPAIGN_SPEC.event_count, + ) + first_missing = payload["first_missing_index"] + if first_missing is not None: + _integer( + first_missing, "first_missing_index", + maximum=FULL_90D_CAMPAIGN_SPEC.event_count - 1, + ) + failure_stage = _text(payload["failure_stage"], "failure_stage") + blockers = _array(payload["blockers"], "blockers") + if len(blockers) != 1 or type(blockers[0]) is not str: + raise ValueError("failure payload requires exactly one reviewed blocker") + if (command, failure_stage, blockers[0]) not in _FAILURE_COMBINATIONS: + raise ValueError("failure command, stage, or blocker is not reviewed") + _safety(payload, require_zero=False) + + +def _validate_plan(payload: dict[str, object]) -> None: + spec = FULL_90D_CAMPAIGN_SPEC + for key, expected in ( + ("operation", _OPERATION), ("command", "plan"), + ("status", "planned_no_connection"), ("campaign_id", spec.campaign_id), + ("campaign_spec_sha256", spec.spec_sha256), + ("corpus_generator_sha256", spec.corpus_generator_sha256), + ("event_count", spec.event_count), ("segment_count", spec.segment_count), + ("timeframe", spec.timeframe), ("expected_run_id_count", spec.event_count), + ("postgresql_version_required", 16), + ("postgresql_host_rule", "literal_loopback_only"), + ("postgresql_database_rule", "secure_eval_phase8b_shadow_campaign_"), + ("database_connection_attempted", False), ("socket_opened", False), + ): + _exact(payload[key], expected, key) + _git_sha(payload["repository_sha"], "repository_sha") + _digest(payload["expected_run_id_set_sha256"], "expected_run_id_set_sha256") + samples = _array(payload["expected_deterministic_run_ids"], "run ID samples") + if len(samples) != len(_PLAN_SAMPLE_INDEXES): + raise ValueError("plan must expose exactly 26 deterministic run ID samples") + for sample, expected_index in zip(samples, _PLAN_SAMPLE_INDEXES): + row = _mapping(sample, ("event_index", "shadow_run_id"), "run ID sample") + _exact(row["event_index"], expected_index, "sample event_index") + _uuid(row["shadow_run_id"], "sample shadow_run_id") + _safety(payload, require_zero=True) + + +def _validate_result(payload: dict[str, object]) -> None: + spec = FULL_90D_CAMPAIGN_SPEC + _exact(payload["operation"], _OPERATION, "operation") + if _text(payload["command"], "command") not in ("run", "resume"): + raise ValueError("result command is not reviewed") + _exact(payload["campaign_id"], spec.campaign_id, "campaign_id") + _exact(payload["campaign_spec_sha256"], spec.spec_sha256, "campaign spec") + _git_sha(payload["repository_sha"], "repository_sha") + _exact(payload["total_event_count"], spec.event_count, "total_event_count") + total = spec.event_count + processed = _integer(payload["processed_event_count"], "processed_event_count", maximum=total) + committed = _integer(payload["committed_row_count"], "committed_row_count", maximum=total) + completed = _integer(payload["completed_event_count"], "completed_event_count", maximum=total) + _exact(payload["missing_event_count"], total - completed, "missing_event_count") + first_missing = payload["first_missing_index"] + if completed == total: + if first_missing is not None: + raise ValueError("complete result cannot have a first missing index") + _exact(payload["status"], "complete", "status") + else: + _exact(first_missing, completed, "first_missing_index") + _exact(payload["status"], "partial", "status") + accepted = _integer(payload["accepted_decision_count"], "accepted count", maximum=completed) + blocked = _integer(payload["blocked_decision_count"], "blocked count", maximum=completed) + if accepted + blocked != completed: + raise ValueError("decision totals disagree with completed events") + intents = _integer(payload["shadow_intent_count"], "intent count", maximum=completed) + if intents < accepted: + raise ValueError("accepted decisions require hypothetical intents") + _blocker_frequencies(payload["blocker_frequencies"], blocked) + remaining = completed + segment_totals = [] + for size in _CANONICAL_SEGMENT_SIZES: + segment_total = min(size, remaining) + segment_totals.append(segment_total) + remaining -= segment_total + _segment_counts( + payload["segment_decision_counts"], accepted_count=accepted, + blocked_count=blocked, expected_segment_totals=tuple(segment_totals), + ) + segment_length, window_length = _prefix_lengths(first_missing) + _hash_prefix( + payload["segment_result_hashes"], + _CANONICAL_SEGMENT_IDS, + segment_length, + "segment_result_hashes", + ) + _hash_prefix( + payload["window_result_hashes"], + tuple(key for key, _ in _CANONICAL_WINDOWS), + window_length, + "window_result_hashes", + ) + _digest(payload["event_sequence_sha256"], "event_sequence_sha256") + _digest(payload["final_decision_chain_sha256"], "final_decision_chain_sha256") + campaign_hash = payload["campaign_result_sha256"] + if completed == total: + _digest(campaign_hash, "campaign_result_sha256") + elif campaign_hash is not None: + raise ValueError("partial result cannot have a campaign result hash") + persisted = _integer(payload["persisted_count"], "persisted_count", maximum=total) + replayed = _integer(payload["replay_count"], "replay_count", maximum=total) + if committed != persisted or processed != persisted + replayed or processed > completed: + raise ValueError("operation persistence counts are inconsistent") + _safety(payload, require_zero=True) + + +def _validate_progress(payload: dict[str, object]) -> None: + spec = FULL_90D_CAMPAIGN_SPEC + _exact(payload["operation"], _OPERATION, "operation") + command = _text(payload["command"], "command") + if command not in ("inspect", "verify"): + raise ValueError("progress command is not reviewed") + _exact(payload["campaign_id"], spec.campaign_id, "campaign_id") + _exact(payload["campaign_spec_sha256"], spec.spec_sha256, "campaign spec") + _git_sha(payload["repository_sha"], "repository_sha") + _exact(payload["total_event_count"], spec.event_count, "total_event_count") + total = spec.event_count + completed = _integer(payload["completed_event_count"], "completed count", maximum=total) + missing = _integer(payload["missing_event_count"], "missing count", maximum=total) + valid = _integer(payload["valid_bundle_count"], "valid bundle count", maximum=total) + invalid = _integer(payload["invalid_bundle_count"], "invalid bundle count", maximum=total) + if completed != valid or missing != total - completed or invalid != 0: + raise ValueError("progress bundle counts are inconsistent") + first_missing = payload["first_missing_index"] + complete = completed == total + if complete: + if first_missing is not None: + raise ValueError("complete progress cannot have a first missing index") + _exact( + payload["status"], + "verified" if command == "verify" else "complete", + "status", + ) + else: + _integer(first_missing, "first_missing_index", maximum=total - 1) + _exact(payload["status"], "incomplete", "status") + completion = _mapping( + payload["segment_completion"], + _CANONICAL_SEGMENT_IDS, + "segment_completion", + ) + segment_totals = tuple( + _integer( + completion[segment_id], + f"segment_completion.{segment_id}", + maximum=size, + ) + for segment_id, size in zip(_CANONICAL_SEGMENT_IDS, _CANONICAL_SEGMENT_SIZES) + ) + if sum(segment_totals) != completed: + raise ValueError("segment completion disagrees with completed events") + accepted = _integer( + payload["accepted_decision_count"], "accepted count", maximum=completed + ) + blocked = _integer( + payload["blocked_decision_count"], "blocked count", maximum=completed + ) + if accepted + blocked != completed: + raise ValueError("decision totals disagree with completed events") + intents = _integer( + payload["shadow_intent_count"], "intent count", maximum=completed + ) + if intents < accepted: + raise ValueError("accepted decisions require hypothetical intents") + _blocker_frequencies(payload["blocker_frequencies"], blocked) + _segment_counts( + payload["segment_decision_counts"], accepted_count=accepted, + blocked_count=blocked, expected_segment_totals=segment_totals, + ) + segment_length, window_length = _prefix_lengths(first_missing) + _hash_prefix( + payload["segment_result_hashes"], + _CANONICAL_SEGMENT_IDS, + segment_length, + "segment_result_hashes", + ) + _hash_prefix( + payload["window_result_hashes"], + tuple(key for key, _ in _CANONICAL_WINDOWS), + window_length, + "window_result_hashes", + ) + _digest(payload["event_sequence_sha256"], "event_sequence_sha256") + _digest(payload["final_decision_chain_sha256"], "final_decision_chain_sha256") + campaign_hash = payload["campaign_result_sha256"] + if complete: + _digest(campaign_hash, "campaign_result_sha256") + elif campaign_hash is not None: + raise ValueError("incomplete progress cannot have a campaign result hash") + _safety(payload, require_zero=True) + + +def _validate_public_output(payload: Mapping[str, object]) -> None: + if type(payload) is not dict or tuple(payload) not in _CLI_PUBLIC_OUTPUT_KEY_ALLOWLISTS: + raise ValueError("campaign CLI output keys differ from the fixed allowlist") + if "failure_stage" in payload: + _validate_failure(payload) + elif payload["command"] == "help": + _validate_help(payload) + elif payload["command"] == "plan": + _validate_plan(payload) + elif "processed_event_count" in payload: + _validate_result(payload) + else: + _validate_progress(payload) + + +def _scan_public_output(value: object, *, nested: bool = False) -> None: + if isinstance(value, Mapping): + for key, item in value.items(): + if type(key) is not str: + raise ValueError("campaign CLI output keys must be text") + if nested and ( + _UNSAFE_NESTED_KEY.search(key) or _UNSAFE_PUBLIC_TEXT.search(key) + ): + raise ValueError("campaign CLI output contains an unsafe nested key") + _scan_public_output(item, nested=True) + return + if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): + for item in value: + _scan_public_output(item, nested=True) + return + if isinstance(value, str) and _UNSAFE_PUBLIC_TEXT.search(value): + raise ValueError("campaign CLI output contains unsafe text") + + +class CampaignCliUsageError(ValueError): + """Argument failure that deliberately carries no caller-provided text.""" + + +class _SafeArgumentParser(argparse.ArgumentParser): + def error(self, message: str) -> None: # pragma: no cover - argparse dispatch + raise CampaignCliUsageError("invalid historical campaign arguments") + + +def _postgres_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--postgres-database", required=True) + parser.add_argument("--postgres-host", default="127.0.0.1") + parser.add_argument("--postgres-port", type=int, default=5432) + parser.add_argument("--postgres-user") + parser.add_argument("--postgres-sslmode", default="disable") + parser.add_argument("--repository-sha") + parser.add_argument("--campaign", choices=(_CAMPAIGN_NAME,), default=_CAMPAIGN_NAME) + + +def _parser() -> argparse.ArgumentParser: + parser = _SafeArgumentParser( + prog="secure-eval-live-shadow-campaign", + add_help=False, + ) + commands = parser.add_subparsers(dest="command", required=True) + plan = commands.add_parser("plan", add_help=False) + plan.add_argument("--campaign", choices=(_CAMPAIGN_NAME,), default=_CAMPAIGN_NAME) + plan.add_argument("--repository-sha", required=True) + for name in ("run", "resume", "inspect", "verify"): + _postgres_arguments(commands.add_parser(name, add_help=False)) + return parser + + +def _zero_facts() -> ShadowSafetyFacts: + return ShadowSafetyFacts(network_read_count=0) + + +def _facts_payload(facts: ShadowSafetyFacts | None = None) -> dict[str, object]: + facts = _zero_facts() if facts is None else facts + return { + "network_read_count": facts.network_read_count, + "network_write_count": facts.network_write_count, + "production_transport_call_count": facts.production_transport_call_count, + "authenticated_endpoint_call_count": facts.authenticated_endpoint_call_count, + "credential_read_count": facts.credential_read_count, + "production_write_count": facts.production_write_count, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + } + + +def _segment_decision_counts_payload( + values: Mapping[str, Mapping[str, int]], +) -> dict[str, dict[str, int]]: + return { + segment_id: { + "accepted": values[segment_id]["accepted"], + "blocked": values[segment_id]["blocked"], + } + for segment_id in _CANONICAL_SEGMENT_IDS + } + + +def _help_payload() -> dict[str, object]: + return { + "operation": _OPERATION, + "command": "help", + "status": "available", + "commands": list(_COMMANDS), + "campaigns": [_CAMPAIGN_NAME], + "password_argument_supported": False, + **_facts_payload(), + } + + +def _failure_payload( + command: str, + blocker: str, + *, + failure_stage: str, + processed_event_count: int = 0, + committed_row_count: int = 0, + first_missing_index: int | None = 0, +) -> dict[str, object]: + return { + "operation": _OPERATION, + "command": command, + "status": "blocked", + "campaign_id": FULL_90D_CAMPAIGN_SPEC.campaign_id, + "processed_event_count": processed_event_count, + "committed_row_count": committed_row_count, + "first_missing_index": first_missing_index, + "failure_stage": failure_stage, + "blockers": [blocker], + **_facts_payload(), + } + + +def _failure_payload_from_progress( + command: str, + progress: Mapping[str, object], + *, + failure_stage: str = "campaign_runtime", + blocker: str = "campaign_runtime_interrupted", +) -> dict[str, object]: + """Project only fixed, validated counters from a structured runtime failure.""" + + if ( + type(command) is not str + or type(failure_stage) is not str + or type(blocker) is not str + or (command, failure_stage, blocker) not in _FAILURE_COMBINATIONS + ): + raise ValueError("campaign failure projection is not reviewed") + if type(progress) is not dict: + raise TypeError("campaign failure progress must be an exact mapping") + counts: dict[str, int] = {} + for key in ( + "processed_event_count", + "committed_row_count", + "network_read_count", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + ): + value = progress.get(key) + if type(value) is not int or value < 0: + raise ValueError("campaign failure progress contains an invalid counter") + counts[key] = value + first_missing_index = progress.get("first_missing_index") + if first_missing_index is not None and ( + type(first_missing_index) is not int or first_missing_index < 0 + ): + raise ValueError("campaign failure progress has an invalid missing index") + payload = _failure_payload( + command, + blocker, + failure_stage=failure_stage, + processed_event_count=counts["processed_event_count"], + committed_row_count=counts["committed_row_count"], + first_missing_index=first_missing_index, + ) + for key in _SAFETY_OUTPUT_KEYS[:6]: + payload[key] = counts[key] + for key in ("production_submit_reachable", "production_cancel_reachable"): + value = progress.get(key) + if type(value) is not bool: + raise ValueError("campaign failure progress has an invalid authority fact") + payload[key] = value + for key in ("real_account_data_used", "operator_database_accessed"): + value = progress.get(key) + if type(value) is not bool: + raise ValueError("campaign failure progress has an invalid data-authority fact") + payload[key] = value + return payload + + +def _serialization_failure_progress( + payload: Mapping[str, object], +) -> tuple[int, int, int | None]: + """Salvage only bounded scalar progress; never inspect unsafe nested data.""" + + def bounded_integer(key: str, *, maximum: int) -> int | None: + try: + value = payload.get(key) + except Exception: + return None + if type(value) is int and 0 <= value <= maximum: + return value + return None + + processed = bounded_integer( + "processed_event_count", maximum=FULL_90D_CAMPAIGN_SPEC.event_count + ) + if processed is None: + processed = bounded_integer( + "completed_event_count", maximum=FULL_90D_CAMPAIGN_SPEC.event_count + ) + committed = bounded_integer( + "committed_row_count", maximum=FULL_90D_CAMPAIGN_SPEC.event_count + ) + try: + first_missing = payload.get("first_missing_index") + except Exception: + first_missing = 0 + if first_missing is not None and ( + type(first_missing) is not int + or not 0 <= first_missing < FULL_90D_CAMPAIGN_SPEC.event_count + ): + first_missing = 0 + return processed or 0, committed or 0, first_missing + +def _emit(payload: Mapping[str, object]) -> bool: + """Emit only JSON; discard serialization details on the failure path.""" + + try: + _validate_public_output(payload) + _scan_public_output(payload) + encoded = json.dumps(dict(payload), ensure_ascii=True, separators=(",", ":")) + except Exception: + processed_event_count, committed_row_count, first_missing_index = ( + _serialization_failure_progress(payload) + ) + encoded = json.dumps( + _failure_payload( + "unknown", + "campaign_result_serialization_failed", + failure_stage="serialization", + processed_event_count=processed_event_count, + committed_row_count=committed_row_count, + first_missing_index=first_missing_index, + ), + ensure_ascii=True, + separators=(",", ":"), + ) + print(encoded) + return False + print(encoded) + return True + + +def _repository_sha(value: str | None) -> str: + observed = resolve_runtime_repository_identity().observed_commit_sha + if value is not None: + reviewed = validate_git_commit_sha(value) + if reviewed != observed: + raise PermissionError( + "historical campaign repository SHA differs from runtime identity" + ) + return observed + + +def _connect(args): + """Validate all target authority before importing a driver or opening a socket.""" + + database = validate_shadow_campaign_database_name(args.postgres_database) + host = validate_shadow_postgres_host(args.postgres_host) + if ( + isinstance(args.postgres_port, bool) + or not isinstance(args.postgres_port, int) + or not 1 <= args.postgres_port <= 65_535 + ): + raise PermissionError("historical campaign PostgreSQL port is invalid") + if args.postgres_sslmode not in {"disable", "require", "verify-ca", "verify-full"}: + raise PermissionError("historical campaign PostgreSQL sslmode is invalid") + try: + import psycopg + except ImportError as exc: + raise RuntimeError("historical campaign requires the postgres extra") from exc + connect_arguments: dict[str, object] = { + "host": host, + "port": args.postgres_port, + "dbname": database, + "sslmode": args.postgres_sslmode, + } + if args.postgres_user: + connect_arguments["user"] = args.postgres_user + # No password parameter exists: authentication is delegated only to libpq. + return database, host, psycopg.connect(**connect_arguments) + + +def _plan_payload(repository_sha: str) -> dict[str, object]: + spec = FULL_90D_CAMPAIGN_SPEC + run_id_chain = sha256_payload( + { + "operation": "phase8b-historical-shadow-run-id-set-v1", + "repository_sha": repository_sha, + "campaign_spec_sha256": spec.spec_sha256, + } + ) + samples: list[dict[str, object]] = [] + for event in iter_shadow_campaign_events(spec): + account = synthetic_account_snapshot_for_event(event.event_index) + run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=repository_sha, + campaign_spec_sha256=spec.spec_sha256, + ) + run_id_chain = sha256_payload( + { + "prior_sha256": run_id_chain, + "event_index": event.event_index, + "shadow_run_id": str(run_id), + } + ) + if event.event_index in _PLAN_SAMPLE_INDEXES: + samples.append( + {"event_index": event.event_index, "shadow_run_id": str(run_id)} + ) + return { + "operation": _OPERATION, + "command": "plan", + "status": "planned_no_connection", + "campaign_id": spec.campaign_id, + "repository_sha": repository_sha, + "campaign_spec_sha256": spec.spec_sha256, + "corpus_generator_sha256": spec.corpus_generator_sha256, + "event_count": spec.event_count, + "segment_count": spec.segment_count, + "timeframe": spec.timeframe, + "expected_run_id_count": spec.event_count, + "expected_run_id_set_sha256": run_id_chain, + "expected_deterministic_run_ids": samples, + "postgresql_version_required": 16, + "postgresql_host_rule": "literal_loopback_only", + "postgresql_database_rule": ( + "secure_eval_phase8b_shadow_campaign_" + ), + "database_connection_attempted": False, + "socket_opened": False, + **_facts_payload(), + } + + +def _result_payload(command: str, result: ShadowCampaignResult) -> dict[str, object]: + return { + "operation": _OPERATION, + "command": command, + "status": "complete" if result.complete else "partial", + "campaign_id": result.campaign_id, + "campaign_spec_sha256": result.campaign_spec_sha256, + "repository_sha": result.repository_sha, + "total_event_count": result.total_event_count, + "processed_event_count": result.processed_event_count, + "committed_row_count": result.committed_event_count, + "completed_event_count": result.completed_event_count, + "missing_event_count": result.total_event_count - result.completed_event_count, + "first_missing_index": result.first_missing_index, + "accepted_decision_count": result.accepted_decision_count, + "blocked_decision_count": result.blocked_decision_count, + "shadow_intent_count": result.shadow_intent_count, + "blocker_frequencies": dict(sorted(result.blocker_frequencies.items())), + "segment_decision_counts": _segment_decision_counts_payload( + result.segment_decision_counts + ), + "segment_result_hashes": { + key: value for key, value in result.segment_result_hashes + }, + "window_result_hashes": { + key: value for key, value in result.window_result_hashes + }, + "event_sequence_sha256": result.event_sequence_sha256, + "final_decision_chain_sha256": result.final_decision_chain_sha256, + "campaign_result_sha256": result.campaign_result_sha256, + "persisted_count": result.persisted_count, + "replay_count": result.replay_count, + **_facts_payload(result.safety_facts), + } + + +def _progress_payload(command: str, progress: ShadowCampaignProgress) -> dict[str, object]: + return { + "operation": _OPERATION, + "command": command, + "status": "verified" if progress.complete and command == "verify" else ( + "complete" if progress.complete else "incomplete" + ), + "campaign_id": progress.campaign_id, + "campaign_spec_sha256": progress.campaign_spec_sha256, + "repository_sha": progress.repository_sha, + "total_event_count": progress.total_event_count, + "completed_event_count": progress.completed_event_count, + "missing_event_count": progress.missing_event_count, + "valid_bundle_count": progress.valid_bundle_count, + "invalid_bundle_count": progress.invalid_bundle_count, + "first_missing_index": progress.first_missing_index, + "segment_completion": { + segment_id: progress.segment_completion[segment_id] + for segment_id in _CANONICAL_SEGMENT_IDS + }, + "accepted_decision_count": progress.accepted_decision_count, + "blocked_decision_count": progress.blocked_decision_count, + "shadow_intent_count": progress.shadow_intent_count, + "blocker_frequencies": dict(sorted(progress.blocker_frequencies.items())), + "segment_decision_counts": _segment_decision_counts_payload( + progress.segment_decision_counts + ), + "segment_result_hashes": { + key: value for key, value in progress.segment_result_hashes + }, + "window_result_hashes": { + key: value for key, value in progress.window_result_hashes + }, + "event_sequence_sha256": progress.event_sequence_sha256, + "final_decision_chain_sha256": progress.decision_chain_sha256, + "campaign_result_sha256": progress.campaign_result_sha256, + **_facts_payload(progress.safety_facts), + } + + +def _persistent_main(args) -> int: + command = str(args.command) + connection = None + try: + repository_sha = _repository_sha(args.repository_sha) + database, host, connection = _connect(args) + repository = PostgresShadowRepository( + connection, + expected_database=database, + expected_host=host, + ) + runtime = ShadowCampaignRuntime(repository, repository_sha) + if command == "run": + payload = _result_payload(command, runtime.run()) + elif command == "resume": + payload = _result_payload(command, runtime.resume()) + elif command == "inspect": + payload = _progress_payload(command, runtime.inspect()) + else: + payload = _progress_payload(command, runtime.verify()) + emitted = _emit(payload) + return 0 if emitted and payload["status"] in {"complete", "verified"} else 2 + except ShadowCampaignOperationFailure as exc: + try: + payload = _failure_payload_from_progress( + command, + _copy_shadow_campaign_failure_progress(exc.progress), + failure_stage=exc.failure_stage, + blocker="campaign_operation_failed_closed", + ) + except Exception: + payload = _failure_payload( + command, + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + ) + _emit(payload) + return 2 + except ShadowCampaignSpecConflict as exc: + try: + if exc.progress is None or exc.failure_stage is None: + raise ValueError("campaign conflict has no operation progress") + payload = _failure_payload_from_progress( + command, + _copy_shadow_campaign_failure_progress(exc.progress), + failure_stage=exc.failure_stage, + blocker="campaign_authority_conflict", + ) + except Exception: + payload = _failure_payload( + command, + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + ) + _emit(payload) + return 2 + except ShadowCampaignInjectedCrash as exc: + try: + payload = _failure_payload_from_progress( + command, + _copy_shadow_campaign_failure_progress(exc.progress), + ) + except Exception: + payload = _failure_payload( + command, + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + ) + _emit(payload) + return 2 + except ShadowInjectedCrash as exc: + try: + payload = _failure_payload_from_progress(command, exc.progress) + except Exception: + payload = _failure_payload( + command, + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + ) + _emit(payload) + return 2 + except ShadowCampaignRepositoryCorruption as exc: + _emit( + _failure_payload( + command, + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + processed_event_count=0, + committed_row_count=0, + first_missing_index=exc.first_invalid_event_index, + ) + ) + return 2 + except Exception: + _emit( + _failure_payload( + command, + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + ) + ) + return 2 + finally: + if connection is not None: + try: + connection.close() + except Exception: + pass + + +def main(argv: Sequence[str] | None = None) -> int: + arguments = list(sys.argv[1:] if argv is None else argv) + if arguments in (["--help"], ["-h"]): + return 0 if _emit(_help_payload()) else 2 + try: + args = _parser().parse_args(arguments) + if args.command == "plan": + payload = _plan_payload(validate_git_commit_sha(args.repository_sha)) + return 0 if _emit(payload) else 2 + return _persistent_main(args) + except (CampaignCliUsageError, SystemExit): + _emit( + _failure_payload( + "unknown", + "invalid_campaign_arguments", + failure_stage="argument_validation", + ) + ) + return 2 + except Exception: + _emit( + _failure_payload( + "plan", + "campaign_plan_failed_closed", + failure_stage="planning", + ) + ) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) + + +__all__ = ["main"] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_corpus.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_corpus.py new file mode 100644 index 0000000..a06de7d --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_corpus.py @@ -0,0 +1,639 @@ +"""Deterministic streaming corpus for the Phase 8B historical shadow campaign. + +The generator is deliberately arithmetic-only: it has no network, environment, +credential, clock, filesystem, random-module, or machine-identity input. The full +25,920-event corpus is yielded one event at a time and is never materialized by the +production API. +""" + +from __future__ import annotations + +from collections.abc import Iterator +from datetime import datetime, timedelta, timezone +from decimal import ( + ROUND_CEILING, + ROUND_FLOOR, + ROUND_HALF_EVEN, + Decimal, + localcontext, +) +from types import MappingProxyType +from typing import Mapping + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .shadow_campaign_expected_hashes import ( + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, +) +from .shadow_campaign_models import ( + ShadowCampaignCorpusHashes, + ShadowCampaignEvent, + ShadowCampaignSegment, + ShadowCampaignSpec, + _validate_shadow_campaign_segment_authority, + campaign_event_id, +) + + +SHADOW_CAMPAIGN_GENERATOR_VERSION = ( + "phase8b-historical-shadow-corpus-generator-v1" +) +SHADOW_CAMPAIGN_VERSION = "phase8b-historical-shadow-campaign-v1" +FULL_90D_CAMPAIGN_ID = "phase8b-shadow-full-90d-v1" +FULL_90D_EVENT_COUNT = 25_920 +FULL_90D_SEGMENT_COUNT = 12 +FULL_90D_EVENTS_PER_SEGMENT = 2_160 +COMPACT_CAMPAIGN_MINIMUM_EVENT_COUNT = 1_440 +FULL_90D_WINDOW_EVENT_COUNTS = MappingProxyType( + {"1d": 288, "7d": 2_016, "30d": 8_640, "90d": 25_920} +) + +_GENERATOR_IMPLEMENTATION_PAYLOAD = MappingProxyType( + { + "generator_version": SHADOW_CAMPAIGN_GENERATOR_VERSION, + "algorithm": "splitmix64-indexed-decimal-regime-walk", + "arithmetic": "integer-and-decimal-only-local-context-precision-50", + "time_source": "fixed-utc-start-plus-five-minute-event-index", + "event_identity": "logical-index-and-point-in-time-data-only", + "streaming": "single-pass-constant-event-memory", + "external_inputs": (), + "network_access": False, + "environment_access": False, + "credential_access": False, + "user_file_access": False, + } +) +SHADOW_CAMPAIGN_GENERATOR_SHA256 = sha256_payload( + _GENERATOR_IMPLEMENTATION_PAYLOAD +) +# Concise aliases for evidence and orchestration callers. +GENERATOR_IMPLEMENTATION_HASH = EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256 +CORPUS_GENERATOR_VERSION = SHADOW_CAMPAIGN_GENERATOR_VERSION + +_REGIME_DEFINITIONS: tuple[tuple[str, int, Mapping[str, object]], ...] = ( + ( + "quiet_range", + 8_101_001, + {"drift_ppm": Decimal("0"), "noise_ppm": 16, "range_ppm": 24, "spread_ppm": 8}, + ), + ( + "low_volatility_uptrend", + 8_102_003, + {"drift_ppm": Decimal("8"), "noise_ppm": 24, "range_ppm": 36, "spread_ppm": 10}, + ), + ( + "low_volatility_downtrend", + 8_103_007, + {"drift_ppm": Decimal("-8"), "noise_ppm": 24, "range_ppm": 36, "spread_ppm": 10}, + ), + ( + "high_volatility_uptrend", + 8_104_009, + {"drift_ppm": Decimal("18"), "noise_ppm": 220, "range_ppm": 280, "spread_ppm": 35}, + ), + ( + "high_volatility_downtrend", + 8_105_013, + {"drift_ppm": Decimal("-18"), "noise_ppm": 220, "range_ppm": 280, "spread_ppm": 35}, + ), + ( + "volatility_spike", + 8_106_017, + {"drift_ppm": Decimal("0"), "noise_ppm": 640, "range_ppm": 900, "spread_ppm": 70}, + ), + ( + "flash_drawdown_and_bounded_rebound", + 8_107_019, + {"drift_ppm": Decimal("0"), "noise_ppm": 90, "range_ppm": 180, "spread_ppm": 45}, + ), + ( + "deterministic_gap_sequence", + 8_108_023, + {"drift_ppm": Decimal("2"), "noise_ppm": 85, "range_ppm": 130, "spread_ppm": 30}, + ), + ( + "illiquid_wide_spread_conditions", + 8_109_029, + {"drift_ppm": Decimal("0"), "noise_ppm": 150, "range_ppm": 240, "spread_ppm": 2_500}, + ), + ( + "stale_missing_duplicate_out_of_order_sequence", + 8_110_031, + {"drift_ppm": Decimal("0"), "noise_ppm": 70, "range_ppm": 110, "spread_ppm": 40}, + ), + ( + "instrument_metadata_boundary_churn", + 8_111_037, + {"drift_ppm": Decimal("1"), "noise_ppm": 55, "range_ppm": 90, "spread_ppm": 28}, + ), + ( + "recovery_and_normalization", + 8_112_041, + {"drift_ppm": Decimal("1"), "noise_ppm": 30, "range_ppm": 45, "spread_ppm": 12}, + ), +) + +_MASK_64 = (1 << 64) - 1 +_MIX_INCREMENT = 0x9E3779B97F4A7C15 +_ONE_MILLION = Decimal("1000000") +_INITIAL_PRICE = Decimal("50000.0") + + +def _segment( + segment_index: int, + regime: str, + seed: int, + parameters: Mapping[str, object], +) -> ShadowCampaignSegment: + return ShadowCampaignSegment( + segment_id=( + f"full-90d-segment-{segment_index + 1:02d}-" + f"{regime.replace('_', '-')}" + ), + segment_index=segment_index, + regime=regime, + start_event_index=segment_index * FULL_90D_EVENTS_PER_SEGMENT, + event_count=FULL_90D_EVENTS_PER_SEGMENT, + seed=seed, + generator_parameters=parameters, + ) + + +def full_90d_campaign_spec() -> ShadowCampaignSpec: + """Construct the canonical repository-owned 90-day campaign specification.""" + + segments = tuple( + _segment(index, regime, seed, parameters) + for index, (regime, seed, parameters) in enumerate(_REGIME_DEFINITIONS) + ) + return ShadowCampaignSpec( + campaign_id=FULL_90D_CAMPAIGN_ID, + campaign_version=SHADOW_CAMPAIGN_VERSION, + corpus_generator_version=SHADOW_CAMPAIGN_GENERATOR_VERSION, + corpus_generator_sha256=SHADOW_CAMPAIGN_GENERATOR_SHA256, + instrument="BTC-USDT", + instrument_type="spot", + timeframe="5m", + start_at_utc=datetime(2024, 1, 1, tzinfo=timezone.utc), + event_count=FULL_90D_EVENT_COUNT, + events_per_segment=FULL_90D_EVENTS_PER_SEGMENT, + equivalent_duration_days=90, + segments=segments, + window_labels=tuple(FULL_90D_WINDOW_EVENT_COUNTS), + window_event_counts=tuple(FULL_90D_WINDOW_EVENT_COUNTS.values()), + ) + + +FULL_90D_CAMPAIGN_SPEC = full_90d_campaign_spec() +FULL_90D_CAMPAIGN_SPEC_SHA256 = FULL_90D_CAMPAIGN_SPEC.spec_sha256 + + +def _mix64(seed: int, index: int, salt: int = 0) -> int: + """Index-addressable SplitMix64 output; independent of iteration/thread order.""" + + value = (seed + ((index + 1) * _MIX_INCREMENT) + salt) & _MASK_64 + value = ((value ^ (value >> 30)) * 0xBF58476D1CE4E5B9) & _MASK_64 + value = ((value ^ (value >> 27)) * 0x94D049BB133111EB) & _MASK_64 + return (value ^ (value >> 31)) & _MASK_64 + + +def _noise(seed: int, index: int, amplitude: int, *, salt: int = 0) -> int: + if amplitude <= 0: + return 0 + return int(_mix64(seed, index, salt) % (2 * amplitude + 1)) - amplitude + + +def _quantize_nearest(value: Decimal, quantum: Decimal) -> Decimal: + return value.quantize(quantum, rounding=ROUND_HALF_EVEN) + + +def _quantize_floor(value: Decimal, quantum: Decimal) -> Decimal: + return value.quantize(quantum, rounding=ROUND_FLOOR) + + +def _quantize_ceiling(value: Decimal, quantum: Decimal) -> Decimal: + return value.quantize(quantum, rounding=ROUND_CEILING) + + +def _metadata( + segment: ShadowCampaignSegment, + offset: int, +) -> tuple[str, Decimal, Decimal, Decimal, Decimal, tuple[str, ...]]: + status = "live" + tick_size = Decimal("0.1") + lot_size = Decimal("0.0001") + minimum_quantity = Decimal("0.0001") + maximum_quantity = Decimal("0.1") + flags: list[str] = [] + if segment.regime == "instrument_metadata_boundary_churn": + phase = (offset // 180) % 4 + flags.append("instrument_metadata_churn") + if phase == 1: + tick_size = Decimal("1") + flags.append("tick_size_boundary") + elif phase == 2: + lot_size = Decimal("0.001") + minimum_quantity = Decimal("0.001") + flags.append("lot_size_boundary") + elif phase == 3: + status = "suspended" if offset % 180 < 12 else "live" + flags.append("instrument_status_boundary") + return ( + status, + tick_size, + lot_size, + minimum_quantity, + maximum_quantity, + tuple(flags), + ) + + +def _source_time_and_flags( + segment: ShadowCampaignSegment, + offset: int, + scheduled_at_utc: datetime, +) -> tuple[datetime, tuple[str, ...], bool]: + flags: list[str] = [] + source_time = scheduled_at_utc + missing = False + if segment.regime == "deterministic_gap_sequence" and offset in { + 360, + 720, + 1_080, + 1_440, + 1_800, + }: + # Represent the unavailable interval with a lagging as-of timestamp. + # A decision must never observe a source timestamp from its future. + source_time -= timedelta(minutes=15) + flags.append("deterministic_source_gap") + if segment.regime == "stale_missing_duplicate_out_of_order_sequence" and offset: + if offset % 503 == 0: + flags.append("missing_observation") + missing = True + elif offset % 401 == 0: + source_time -= timedelta(minutes=15) + flags.append("out_of_order_timestamp") + elif offset % 307 == 0: + source_time -= timedelta(minutes=5) + flags.append("duplicate_timestamp") + elif offset % 211 == 0: + source_time -= timedelta(minutes=30) + flags.append("stale_timestamp") + if source_time > scheduled_at_utc: + raise ValueError( + "generated market timestamp cannot be later than its decision time" + ) + return source_time, tuple(flags), missing + + +def _return_ppm(segment: ShadowCampaignSegment, offset: int) -> Decimal: + parameters = segment.generator_parameters + drift = Decimal(parameters["drift_ppm"]) + noise = _noise( + segment.seed, + offset, + int(parameters["noise_ppm"]), + salt=0xA5A5, + ) + result = drift + Decimal(noise) + if segment.regime == "volatility_spike" and 1_020 <= offset < 1_140: + result += Decimal(_noise(segment.seed, offset, 1_600, salt=0x5151)) + elif segment.regime == "flash_drawdown_and_bounded_rebound": + if 720 <= offset < 732: + result -= Decimal("3500") + elif 732 <= offset < 804: + result += Decimal("570") + elif segment.regime == "deterministic_gap_sequence" and offset in { + 360, + 720, + 1_080, + 1_440, + 1_800, + }: + result += Decimal("1800" if (offset // 360) % 2 else "-1800") + return result + + +def _generate_event( + spec: ShadowCampaignSpec, + segment: ShadowCampaignSegment, + offset: int, + previous_close: Decimal, +) -> ShadowCampaignEvent: + event_index = segment.start_event_index + offset + scheduled_at = spec.start_at_utc + timedelta(minutes=5 * event_index) + source_time, source_flags, missing = _source_time_and_flags( + segment, offset, scheduled_at + ) + ( + status, + tick_size, + lot_size, + minimum_quantity, + maximum_quantity, + metadata_flags, + ) = _metadata(segment, offset) + parameters = segment.generator_parameters + + with localcontext() as context: + context.prec = 50 + context.rounding = ROUND_HALF_EVEN + open_price = _quantize_nearest(previous_close, tick_size) + return_ppm = _return_ppm(segment, offset) + unrounded_close = open_price * (_ONE_MILLION + return_ppm) / _ONE_MILLION + close_price = _quantize_nearest(max(tick_size, unrounded_close), tick_size) + range_ppm = int(parameters["range_ppm"]) + abs( + _noise(segment.seed, offset, int(parameters["range_ppm"]), salt=0xB7B7) + ) + range_amount = max( + tick_size, + max(open_price, close_price) * Decimal(range_ppm) / _ONE_MILLION, + ) + high_price = _quantize_ceiling( + max(open_price, close_price) + range_amount, tick_size + ) + low_price = _quantize_floor( + max(tick_size, min(open_price, close_price) - range_amount), tick_size + ) + spread_ppm = int(parameters["spread_ppm"]) + abs( + _noise(segment.seed, offset, max(1, int(parameters["spread_ppm"]) // 3), salt=0xC9C9) + ) + half_spread = close_price * Decimal(spread_ppm) / (2 * _ONE_MILLION) + bid_price = _quantize_floor(max(tick_size, close_price - half_spread), tick_size) + ask_price = _quantize_ceiling(close_price + half_spread, tick_size) + volume_units = 1_000 + int(_mix64(segment.seed, offset, 0xDADA) % 9_001) + volume = Decimal(volume_units) / Decimal("1000") + if segment.regime == "illiquid_wide_spread_conditions": + volume = volume / Decimal("20") + if missing: + volume = Decimal("0") + + direction = "long" if ((event_index // 72) + segment.segment_index) % 2 == 0 else "short" + quantity = Decimal("0.0198") if segment.segment_index == 1 else Decimal("0.01") + if quantity < minimum_quantity: + quantity = minimum_quantity + limit_price = ask_price if direction == "long" else bid_price + + quality_flags = tuple(sorted(set(source_flags + metadata_flags))) + event_id = campaign_event_id(event_index=event_index) + return ShadowCampaignEvent( + campaign_id=spec.campaign_id, + campaign_spec_sha256=spec.spec_sha256, + corpus_generator_version=spec.corpus_generator_version, + corpus_generator_sha256=spec.corpus_generator_sha256, + event_index=event_index, + event_id=event_id, + segment_id=segment.segment_id, + segment_index=segment.segment_index, + segment_event_index=offset, + scheduled_at_utc=scheduled_at, + market_timestamp_utc=source_time, + provider="repository_fixture", + instrument=spec.instrument, + instrument_type=spec.instrument_type, + timeframe=spec.timeframe, + open_price=open_price, + high_price=high_price, + low_price=low_price, + close_price=close_price, + bid_price=bid_price, + ask_price=ask_price, + volume=volume, + instrument_status=status, + settlement_asset="USDT", + tick_size=tick_size, + lot_size=lot_size, + minimum_quantity=minimum_quantity, + maximum_quantity=maximum_quantity, + direction=direction, + quantity=quantity, + limit_price=limit_price, + order_type="limit", + quality_flags=quality_flags, + ) + + +def _validate_campaign_spec_authority( + spec: ShadowCampaignSpec, +) -> tuple[ShadowCampaignSegment, ...]: + """Reject injected spec authority before reading any campaign property.""" + + if type(spec) is not ShadowCampaignSpec: + raise TypeError("spec must be the exact ShadowCampaignSpec type") + for name in ( + "campaign_id", + "campaign_version", + "corpus_generator_version", + "corpus_generator_sha256", + "instrument", + "instrument_type", + "timeframe", + ): + if type(object.__getattribute__(spec, name)) is not str: + raise TypeError(f"spec {name} must be exact text") + if type(object.__getattribute__(spec, "start_at_utc")) is not datetime: + raise TypeError("spec start_at_utc must be an exact datetime") + for name in ( + "event_count", + "events_per_segment", + "equivalent_duration_days", + ): + if type(object.__getattribute__(spec, name)) is not int: + raise TypeError(f"spec {name} must be an exact integer") + for name in ( + "repository_owned", + "network_access_allowed", + "credential_access_allowed", + "user_file_access_allowed", + ): + if type(object.__getattribute__(spec, name)) is not bool: + raise TypeError(f"spec {name} must be an exact boolean") + segments = object.__getattribute__(spec, "segments") + if type(segments) is not tuple: + raise TypeError("spec segments must use an exact tuple") + if not segments or any( + type(segment) is not ShadowCampaignSegment for segment in segments + ): + raise TypeError( + "spec segments must contain exact ShadowCampaignSegment values" + ) + for segment in segments: + _validate_shadow_campaign_segment_authority(segment) + labels = object.__getattribute__(spec, "window_labels") + counts = object.__getattribute__(spec, "window_event_counts") + if ( + type(labels) is not tuple + or any(type(label) is not str for label in labels) + or type(counts) is not tuple + or any(type(count) is not int for count in counts) + ): + raise TypeError("spec windows must use exact tuple/scalar types") + return segments + + +def iter_shadow_campaign_events( + spec: ShadowCampaignSpec = FULL_90D_CAMPAIGN_SPEC, + *, + start_index: int = 0, + stop_index: int | None = None, +) -> Iterator[ShadowCampaignEvent]: + """Yield canonical events in order using constant event memory. + + ``stop_index`` is exclusive. Reconstructing a later slice still walks the + deterministic prefix internally so that every price equals the full campaign. + """ + + segments = _validate_campaign_spec_authority(spec) + if type(start_index) is not int or start_index < 0: + raise ValueError("start_index must be a non-negative integer") + if stop_index is None: + stop_index = spec.event_count + if type(stop_index) is not int: + raise ValueError("stop_index must be an integer") + if not start_index <= stop_index <= spec.event_count: + raise ValueError("campaign event slice is outside the specification") + + previous_close = _INITIAL_PRICE + for segment in segments: + for offset in range(segment.event_count): + event = _generate_event(spec, segment, offset, previous_close) + previous_close = event.close_price + if event.event_index >= stop_index: + return + if event.event_index >= start_index: + yield event + + +# Public semantic alias: both names return an iterator and never a full tuple. +stream_campaign_events = iter_shadow_campaign_events + + +def iter_compact_shadow_campaign_events( + *, + event_count: int = COMPACT_CAMPAIGN_MINIMUM_EVENT_COUNT, + spec: ShadowCampaignSpec = FULL_90D_CAMPAIGN_SPEC, +) -> Iterator[ShadowCampaignEvent]: + """Return the canonical full-spec prefix used by cross-platform CI.""" + + _validate_campaign_spec_authority(spec) + if ( + type(event_count) is not int + or event_count < COMPACT_CAMPAIGN_MINIMUM_EVENT_COUNT + or event_count > spec.event_count + ): + raise ValueError( + "compact campaign event_count must be between 1,440 and the full campaign" + ) + return iter_shadow_campaign_events(spec, stop_index=event_count) + + +def _sequence_seed() -> str: + return sha256_payload( + { + "operation": "phase8b-shadow-campaign-data-event-sequence-v2", + } + ) + + +def calculate_shadow_campaign_corpus_hashes( + spec: ShadowCampaignSpec = FULL_90D_CAMPAIGN_SPEC, + *, + event_limit: int | None = None, +) -> ShadowCampaignCorpusHashes: + """Calculate bounded aggregate hashes in one streaming traversal.""" + + _validate_campaign_spec_authority(spec) + if event_limit is None: + event_limit = spec.event_count + if ( + type(event_limit) is not int + or not 1 <= event_limit <= spec.event_count + ): + raise ValueError("event_limit must be within the campaign") + + sequence_hash = _sequence_seed() + current_segment: ShadowCampaignSegment | None = None + segment_chain = "" + segment_results: list[tuple[str, str]] = [] + window_results: list[tuple[str, str]] = [] + windows_by_count = { + count: label for label, count in zip(spec.window_labels, spec.window_event_counts) + } + + for event in iter_shadow_campaign_events(spec, stop_index=event_limit): + sequence_hash = sha256_payload( + {"prior_sha256": sequence_hash, "event_sha256": event.event_sha256} + ) + segment = spec.segments[event.segment_index] + if current_segment is None or current_segment.segment_id != segment.segment_id: + current_segment = segment + segment_chain = sha256_payload( + { + "operation": "phase8b-shadow-campaign-local-segment-events-v2", + } + ) + segment_chain = sha256_payload( + {"prior_sha256": segment_chain, "event_sha256": event.event_sha256} + ) + processed_count = event.event_index + 1 + if processed_count == segment.end_event_index_exclusive: + segment_results.append( + ( + segment.segment_id, + sha256_payload( + { + "operation": "phase8b-shadow-campaign-segment-data-result-v2", + "segment_sha256": segment.segment_sha256, + "event_count": segment.event_count, + "final_chain_sha256": segment_chain, + } + ), + ) + ) + if processed_count in windows_by_count: + window_results.append((windows_by_count[processed_count], sequence_hash)) + + complete = event_limit == spec.event_count + campaign_result = sha256_payload( + { + "operation": "phase8b-shadow-campaign-corpus-result-v1", + "campaign_spec_sha256": spec.spec_sha256, + "corpus_generator_sha256": spec.corpus_generator_sha256, + "event_count": event_limit, + "event_sequence_sha256": sequence_hash, + "segment_result_hashes": tuple(segment_results), + "window_result_hashes": tuple(window_results), + "complete": complete, + } + ) + return ShadowCampaignCorpusHashes( + event_count=event_limit, + event_sequence_sha256=sequence_hash, + segment_result_hashes=tuple(segment_results), + window_result_hashes=tuple(window_results), + campaign_result_sha256=campaign_result, + complete=complete, + ) + + +__all__ = [ + "COMPACT_CAMPAIGN_MINIMUM_EVENT_COUNT", + "CORPUS_GENERATOR_VERSION", + "FULL_90D_CAMPAIGN_ID", + "FULL_90D_CAMPAIGN_SPEC", + "FULL_90D_CAMPAIGN_SPEC_SHA256", + "FULL_90D_EVENT_COUNT", + "FULL_90D_EVENTS_PER_SEGMENT", + "FULL_90D_SEGMENT_COUNT", + "FULL_90D_WINDOW_EVENT_COUNTS", + "GENERATOR_IMPLEMENTATION_HASH", + "SHADOW_CAMPAIGN_GENERATOR_SHA256", + "SHADOW_CAMPAIGN_GENERATOR_VERSION", + "SHADOW_CAMPAIGN_VERSION", + "calculate_shadow_campaign_corpus_hashes", + "full_90d_campaign_spec", + "iter_compact_shadow_campaign_events", + "iter_shadow_campaign_events", + "stream_campaign_events", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_evidence.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_evidence.py new file mode 100644 index 0000000..83fe839 --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_evidence.py @@ -0,0 +1,664 @@ +"""Fixed-allowlist public evidence for the Phase 8B historical shadow campaign.""" +from __future__ import annotations + +import hashlib +import json +import math +import re +from collections.abc import Mapping, Sequence +from pathlib import Path + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .identity import validate_git_commit_sha +from .migration_catalog import ( + CANONICAL_MIGRATION_ROWS, + MIGRATION_0026_SHA256, +) +from .shadow_campaign_expected_hashes import EXPECTED_FULL_SEGMENT_HASHES +from .shadow_campaign_verifier import ( + CAMPAIGN_VERIFIER_VERSION, + POSTGRESQL_CAMPAIGN_NOT_EXECUTED, + PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY, + passed_campaign_case_count, + run_historical_shadow_campaign_verifier, + validate_historical_shadow_campaign_verifier_result, +) + + +ACCEPTED_SHADOW_BASELINE_MERGE_SHA = ( + "376aa35e5b8b77d67b24efca5bbc9fffc95137a5" +) + +CAMPAIGN_PUBLIC_EVIDENCE_KEYS = ( + "schema_version", + "operation", + "status", + "repository_sha", + "accepted_shadow_baseline_merge_sha", + "campaign_version", + "verifier_version", + "verifier_result_sha256", + "corpus_generator_version", + "corpus_spec_sha256", + "corpus_generator_sha256", + "synthetic_account_timeline_sha256", + "timeframe", + "equivalent_duration_days", + "event_count", + "segment_count", + "segment_result_hashes", + "window_result_hashes", + "accepted_decision_count", + "blocked_decision_count", + "shadow_intent_count", + "blocker_frequencies", + "restart_cases_passed", + "replay_cases_passed", + "mutation_cases_passed", + "anti_lookahead_cases_passed", + "concurrency_cases_passed", + "runtime_crash_cases_passed", + "campaign_crash_cases_passed", + "corrupted_bundle_rejections", + "final_decision_chain_sha256", + "campaign_result_sha256", + "postgresql_campaign_classification", + "postgresql_campaign_event_count", + "public_network_smoke_executed", + "operator_bootstrap_executed", + "authenticated_proof_executed", + "real_proof_authorization", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + "production_submit_reachable", + "production_cancel_reachable", + "migration_count", + "latest_migration", + "migration_0026_sha256", + "migration_0027_exists", + "independent_audit_status", + "evidence_payload_sha256", +) + +_FORBIDDEN_KEY = re.compile( + r"(?:api[-_]?key|secret|password|passphrase|" + r"account[-_]?(?:fingerprint|uid)|real[-_]?(?:account[-_]?uid|uid)|" + r"actual[-_]?(?:balance|position|order)|" + r"raw[-_]?(?:balance|position|order|trade|market)|" + r"postgres(?:ql)?[-_]?password|dsn|environment[-_]?dump|" + r"private[-_]?(?:strategy|alpha)|proprietary[-_]?(?:strategy|signal))", + re.I, +) +_FORBIDDEN_COMPONENT = re.compile( + r"(?:^|_)(?:accounts?|account_?ids?|uids?|fingerprints?|" + r"balances?|positions?|pending_?orders?|orders?|trades?|trade_?logs?|fills?|" + r"equity|pnl|profits?|returns?|sharpe(?:_ratio)?|cagr|performance|edge|" + r"win_?rate|api_?(?:keys?|tokens?)|private_?keys?|secrets?|passwords?|" + r"passphrases?|credentials?|access_?tokens?|refresh_?tokens?|authorization|" + r"cookies?|sessions?|database_?urls?|dsns?|usernames?|private|proprietary|" + r"strateg(?:y|ies)|alphas?|params?|parameters?|features?|weights?|signals?)" + r"(?:$|_)", + re.I, +) +_SECRET_VALUE = re.compile( + r"(?:authorization\s*[:=]|(?:api|access|private|refresh)[_-]?" + r"(?:key|sign|token|passphrase)\s*[:=]|" + r"bearer\s+[A-Za-z0-9._~+/=-]+)", + re.I, +) +_LOCAL_PATH = re.compile( + r"(?:[A-Za-z]:[\\/]|\\\\[^\\]+[\\/]|/(?:home|users|tmp|root|workspace)(?:/|$)|" + r"/var/private(?:/|$)|(?:^|[\\/])\.\.(?:[\\/]|$))", + re.I, +) +_PRIVATE_STRATEGY = re.compile( + r"(?:private\s+(?:strategy|alpha)|proprietary\s+(?:strategy|signal)|" + r"expected\s+return|strategy\s+edge|\bsharpe\b|\bcagr\b)", + re.I, +) +_HIGH_ENTROPY = re.compile(r"^[A-Za-z0-9+/=_-]{40,}$") +_JWT = re.compile( + r"(?:^|[^A-Za-z0-9_-])[A-Za-z0-9_-]{8,}\." + r"[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:$|[^A-Za-z0-9_-])" +) +_SAFE_PUBLIC_NAME = re.compile(r"^[a-z][a-z0-9_-]{0,127}$") +_SHA256 = re.compile(r"^[0-9a-f]{64}$") + +_CONCRETE_PATH_TYPE = type(Path()) + +def _case_count(verifier: Mapping[str, object], key: str) -> int: + return passed_campaign_case_count(verifier, key) + + +def _derived_evidence_fields(verifier: Mapping[str, object]) -> dict[str, object]: + zero = verifier["zero_write_facts"] + if type(zero) is not dict: + raise ValueError("campaign verifier zero-write facts are malformed") + postgres = verifier["postgresql_campaign"] + if type(postgres) is not dict: + raise ValueError("campaign verifier PostgreSQL classification is malformed") + return { + "campaign_version": verifier["campaign_version"], + "verifier_version": verifier["verifier_version"], + "verifier_result_sha256": verifier["verifier_result_sha256"], + "corpus_generator_version": verifier["corpus_generator_version"], + "corpus_spec_sha256": verifier["corpus_spec_sha256"], + "corpus_generator_sha256": verifier["corpus_generator_sha256"], + "synthetic_account_timeline_sha256": verifier[ + "synthetic_account_timeline_sha256" + ], + "timeframe": verifier["timeframe"], + "equivalent_duration_days": verifier["equivalent_duration_days"], + "event_count": verifier["event_count"], + "segment_count": verifier["segment_count"], + "segment_result_hashes": verifier["segment_result_hashes"], + "window_result_hashes": verifier["window_result_hashes"], + "accepted_decision_count": verifier["accepted_decision_count"], + "blocked_decision_count": verifier["blocked_decision_count"], + "shadow_intent_count": verifier["shadow_intent_count"], + "blocker_frequencies": verifier["blocker_frequencies"], + "restart_cases_passed": _case_count(verifier, "restart_results"), + "replay_cases_passed": _case_count(verifier, "replay_results"), + "mutation_cases_passed": _case_count(verifier, "mutation_results"), + "anti_lookahead_cases_passed": _case_count( + verifier, "anti_lookahead_results" + ), + "concurrency_cases_passed": _case_count(verifier, "concurrency_results"), + "runtime_crash_cases_passed": _case_count( + verifier, "runtime_crash_results" + ), + "campaign_crash_cases_passed": _case_count( + verifier, "campaign_crash_results" + ), + "corrupted_bundle_rejections": verifier["corrupted_bundle_rejections"], + "final_decision_chain_sha256": verifier[ + "final_decision_chain_sha256" + ], + "campaign_result_sha256": verifier["campaign_result_sha256"], + "postgresql_campaign_classification": postgres["classification"], + "postgresql_campaign_event_count": postgres["event_count"], + "network_write_count": zero["network_write_count"], + "production_transport_call_count": zero[ + "production_transport_call_count" + ], + "authenticated_endpoint_call_count": zero[ + "authenticated_endpoint_call_count" + ], + "credential_read_count": zero["credential_read_count"], + "production_write_count": zero["production_write_count"], + } + + +def _migration_boundary() -> tuple[int, str, str, bool]: + """Bind evidence to the actual normalized repository migration files.""" + + migration_root = Path(__file__).resolve().parents[3] / "db" / "migrations" + files = tuple(sorted(migration_root.glob("*.sql"), key=lambda path: path.name)) + rows = tuple( + ( + path.stem, + path.name, + hashlib.sha256(path.read_bytes().replace(b"\r\n", b"\n")).hexdigest(), + ) + for path in files + ) + if rows != CANONICAL_MIGRATION_ROWS: + raise PermissionError( + "historical campaign evidence requires exact immutable migrations 0001-0026" + ) + migration_0027_exists = any(row[0].startswith("0027") for row in rows) + return ( + len(rows), + rows[-1][0][:4], + rows[-1][2], + migration_0027_exists, + ) + + +def _validate_exact_json_tree( + value: object, + *, + active_container_ids: set[int] | None = None, +) -> None: + """Reject behavior-bearing JSON subclasses before any tree traversal.""" + + value_type = type(value) + if value_type in (str, int, bool) or value is None: + return + if value_type is float: + if not math.isfinite(value): + raise ValueError("campaign evidence JSON numbers must be finite") + return + if value_type not in (dict, list): + raise TypeError("campaign evidence must use exact JSON value types") + + active = active_container_ids if active_container_ids is not None else set() + identity = id(value) + if identity in active: + raise ValueError("campaign evidence JSON must not contain cycles") + active.add(identity) + try: + if value_type is dict: + for key, item in value.items(): + if type(key) is not str: + raise TypeError("campaign evidence JSON keys must be exact text") + _validate_exact_json_tree(item, active_container_ids=active) + else: + for item in value: + _validate_exact_json_tree(item, active_container_ids=active) + finally: + active.remove(identity) + + +def canonical_public_historical_shadow_campaign_evidence_bytes( + payload: Mapping[str, object], +) -> bytes: + """Encode the one accepted artifact form: UTF-8, LF, and one final newline.""" + + if type(payload) is not dict: + raise TypeError("campaign evidence must use an exact JSON object") + _validate_exact_json_tree(payload) + return (json.dumps(payload, indent=2, ensure_ascii=False, allow_nan=False) + "\n").encode("utf-8") + + +def _reject_duplicate_json_object( + pairs: list[tuple[str, object]], +) -> dict[str, object]: + value: dict[str, object] = {} + for key, item in pairs: + if key in value: + raise ValueError("campaign evidence JSON contains a duplicate key") + value[key] = item + return value + + +def _reject_non_json_constant(value: str) -> object: + raise ValueError(f"campaign evidence JSON contains invalid constant: {value}") + + +def load_public_historical_shadow_campaign_evidence( + path: str | Path, +) -> dict[str, object]: + """Load only duplicate-free canonical artifact bytes from a local path.""" + + if type(path) not in (str, _CONCRETE_PATH_TYPE): + raise TypeError( + "campaign evidence path must be exact text or an exact concrete Path" + ) + raw = Path(path).read_bytes() + try: + payload = json.loads( + raw.decode("utf-8"), + object_pairs_hook=_reject_duplicate_json_object, + parse_constant=_reject_non_json_constant, + ) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise ValueError("campaign evidence is not strict UTF-8 JSON") from exc + if type(payload) is not dict: + raise TypeError("campaign evidence must use an exact JSON object") + if raw != canonical_public_historical_shadow_campaign_evidence_bytes(payload): + raise ValueError( + "campaign evidence bytes are not canonical UTF-8/LF with one final newline" + ) + return payload + + +def build_public_historical_shadow_campaign_evidence( + *, repository_sha: str +) -> dict[str, object]: + """Run the executable verifier and build the exact public evidence record.""" + + if type(repository_sha) is not str: + raise TypeError("campaign evidence repository SHA must be exact text") + repository_sha = validate_git_commit_sha(repository_sha) + verifier = run_historical_shadow_campaign_verifier(repository_sha) + validate_historical_shadow_campaign_verifier_result( + verifier, repository_sha=repository_sha + ) + derived = _derived_evidence_fields(verifier) + migration_count, latest_migration, migration_0026_sha256, migration_0027_exists = _migration_boundary() + core: dict[str, object] = { + "schema_version": 1, + "operation": "phase8b_historical_shadow_campaign", + "status": "implemented_pending_independent_audit", + "repository_sha": repository_sha, + "accepted_shadow_baseline_merge_sha": ACCEPTED_SHADOW_BASELINE_MERGE_SHA, + **derived, + "public_network_smoke_executed": False, + "operator_bootstrap_executed": False, + "authenticated_proof_executed": False, + "real_proof_authorization": "NO", + "production_submit_reachable": False, + "production_cancel_reachable": False, + "migration_count": migration_count, + "latest_migration": latest_migration, + "migration_0026_sha256": migration_0026_sha256, + "migration_0027_exists": migration_0027_exists, + "independent_audit_status": "pending", + } + values = dict(core) + values["evidence_payload_sha256"] = sha256_payload(core) + payload = {key: values[key] for key in CAMPAIGN_PUBLIC_EVIDENCE_KEYS} + _validate_public_historical_shadow_campaign_evidence_against_result( + payload, verifier_result=verifier, + ) + return payload + + +def _normalized_security_text(value: str) -> str: + separated = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", value) + return re.sub(r"[^a-z0-9]+", "_", separated.lower()).strip("_") + + +def _contains_forbidden_public_component(value: str) -> bool: + return bool( + _FORBIDDEN_KEY.search(value) + or _FORBIDDEN_COMPONENT.search(_normalized_security_text(value)) + or _SECRET_VALUE.search(value) + or _LOCAL_PATH.search(value) + or _PRIVATE_STRATEGY.search(value) + or _JWT.search(value) + ) + + +def _scan_public_value(value: object, *, path: tuple[str, ...] = ()) -> None: + if isinstance(value, Mapping): + for key, item in value.items(): + key_text = str(key) + # Root keys are already an exact immutable allowlist. Blocker labels + # are separately restricted to the fixed shared-policy vocabulary. + key_is_schema_allowlisted = not path + key_is_blocker_vocabulary = path == ("blocker_frequencies",) + key_is_fixed_hash_catalog = path in { + ("segment_result_hashes",), + ("window_result_hashes",), + } + if ( + not key_is_schema_allowlisted + and not key_is_blocker_vocabulary + and not key_is_fixed_hash_catalog + and _contains_forbidden_public_component(key_text) + ): + raise ValueError( + "forbidden historical campaign evidence key: " + + ".".join(path + (key_text,)) + ) + _scan_public_value(item, path=path + (key_text,)) + return + if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): + for index, item in enumerate(value): + _scan_public_value(item, path=path + (str(index),)) + return + if not isinstance(value, str): + return + if _SECRET_VALUE.search(value) or _JWT.search(value): + raise ValueError("secret/JWT-shaped value in historical campaign evidence") + if _LOCAL_PATH.search(value): + raise ValueError("local path in historical campaign evidence") + if _PRIVATE_STRATEGY.search(value): + raise ValueError("private-strategy/performance claim in campaign evidence") + if _FORBIDDEN_KEY.search(value) or _FORBIDDEN_COMPONENT.search( + _normalized_security_text(value) + ): + raise ValueError("account/secret/private-shaped value in campaign evidence") + key = path[-1] if path else "" + parent = path[-2] if len(path) > 1 else "" + classified = ( + key.endswith(("_sha", "_sha256", "_hash", "_classification")) + or key in { + "repository_sha", "campaign_version", "verifier_version", + "corpus_generator_version", + } + or (parent.endswith("_result_hashes") and _SHA256.fullmatch(value)) + ) + if not classified and _HIGH_ENTROPY.fullmatch(value): + raise ValueError("unclassified high-entropy value in campaign evidence") + + +def _validate_public_historical_shadow_campaign_evidence_against_result( + payload: Mapping[str, object], + *, + verifier_result: Mapping[str, object], +) -> None: + """Validate public fields against the result executed in this call chain.""" + + if type(payload) is not dict: + raise TypeError("campaign evidence must use an exact JSON object") + if type(verifier_result) is not dict: + raise TypeError("campaign verifier result must use an exact JSON object") + _validate_exact_json_tree(payload) + if tuple(payload.keys()) != CAMPAIGN_PUBLIC_EVIDENCE_KEYS: + raise ValueError("campaign evidence keys/order differ from the fixed allowlist") + core = { + key: payload[key] + for key in CAMPAIGN_PUBLIC_EVIDENCE_KEYS + if key != "evidence_payload_sha256" + } + if payload["evidence_payload_sha256"] != sha256_payload(core): + raise ValueError("campaign evidence payload hash mismatch") + if type(payload["repository_sha"]) is not str: + raise TypeError("campaign evidence repository SHA must be exact text") + repository_sha = validate_git_commit_sha(payload["repository_sha"]) + validate_historical_shadow_campaign_verifier_result( + verifier_result, + repository_sha=repository_sha, + ) + if verifier_result["repository_sha"] != repository_sha: + raise ValueError("campaign verifier result is bound to another repository") + verifier_core = { + key: value for key, value in verifier_result.items() + if key != "verifier_result_sha256" + } + if verifier_result.get("verifier_result_sha256") != sha256_payload(verifier_core): + raise ValueError("campaign verifier result self-hash is invalid") + derived = _derived_evidence_fields(verifier_result) + if any(payload[key] != value for key, value in derived.items()): + raise ValueError("campaign evidence claim differs from executable verifier") + + mapping_fields = { + "segment_result_hashes", "window_result_hashes", "blocker_frequencies", + } + integer_fields = { + "schema_version", "equivalent_duration_days", "event_count", + "segment_count", "accepted_decision_count", "blocked_decision_count", + "shadow_intent_count", "restart_cases_passed", "replay_cases_passed", + "mutation_cases_passed", "anti_lookahead_cases_passed", + "concurrency_cases_passed", "runtime_crash_cases_passed", + "campaign_crash_cases_passed", "corrupted_bundle_rejections", + "postgresql_campaign_event_count", "network_write_count", + "production_transport_call_count", "authenticated_endpoint_call_count", + "credential_read_count", "production_write_count", "migration_count", + } + boolean_fields = { + "public_network_smoke_executed", "operator_bootstrap_executed", + "authenticated_proof_executed", "production_submit_reachable", + "production_cancel_reachable", "migration_0027_exists", + } + string_fields = set(CAMPAIGN_PUBLIC_EVIDENCE_KEYS) - { + *mapping_fields, *integer_fields, *boolean_fields, + } + if any(type(payload[key]) is not dict for key in mapping_fields): + raise TypeError("campaign evidence mapping fields must use exact JSON objects") + if any(type(payload[key]) is not int for key in integer_fields): + raise TypeError("campaign evidence integer fields must use exact integers") + if any(type(payload[key]) is not bool for key in boolean_fields): + raise TypeError("campaign evidence boolean fields must use exact booleans") + if any(type(payload[key]) is not str for key in string_fields): + raise TypeError("campaign evidence text fields must use exact strings") + + accepted = payload["accepted_decision_count"] + blocked = payload["blocked_decision_count"] + intent_count = payload["shadow_intent_count"] + if ( + accepted < 0 + or blocked < 0 + or accepted + blocked != 25_920 + or not accepted <= intent_count <= 25_920 + ): + raise ValueError("campaign evidence decision/intent totals are invalid") + if ( + payload["equivalent_duration_days"] != 90 + or payload["event_count"] != 25_920 + or payload["segment_count"] != 12 + or payload["corrupted_bundle_rejections"] != 1 + ): + raise ValueError("campaign evidence fixed scale/corruption facts are invalid") + if ( + payload["schema_version"] != 1 + or payload["operation"] != "phase8b_historical_shadow_campaign" + or payload["status"] != "implemented_pending_independent_audit" + or payload["accepted_shadow_baseline_merge_sha"] + != ACCEPTED_SHADOW_BASELINE_MERGE_SHA + or payload["verifier_version"] != CAMPAIGN_VERIFIER_VERSION + or payload["independent_audit_status"] != "pending" + ): + raise ValueError("campaign evidence status/identity is invalid") + for key in ( + "verifier_result_sha256", + "corpus_spec_sha256", + "corpus_generator_sha256", + "synthetic_account_timeline_sha256", + "final_decision_chain_sha256", + "campaign_result_sha256", + "evidence_payload_sha256", + "migration_0026_sha256", + ): + if _SHA256.fullmatch(payload[key]) is None: + raise ValueError(f"{key} is not a canonical SHA-256 digest") + for field in ("segment_result_hashes", "window_result_hashes"): + hashes = payload[field] + if not hashes: + raise ValueError(f"{field} must be a non-empty ordered mapping") + for digest in hashes.values(): + if type(digest) is not str or _SHA256.fullmatch(digest) is None: + raise ValueError(f"{field} contains a non-SHA-256 value") + if ( + payload["segment_count"] != len(EXPECTED_FULL_SEGMENT_HASHES) + or tuple(payload["segment_result_hashes"]) + != tuple(EXPECTED_FULL_SEGMENT_HASHES) + ): + raise ValueError("campaign evidence segment hash vocabulary/order is invalid") + if tuple(payload["window_result_hashes"]) != ("1d", "7d", "30d", "90d"): + raise ValueError("campaign evidence window hash vocabulary/order is invalid") + blockers = payload["blocker_frequencies"] + if ( + tuple(blockers) != tuple(sorted(blockers)) + or any( + type(name) is not str + or name not in PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY + or _SAFE_PUBLIC_NAME.fullmatch(name) is None + or type(count) is not int + or not 1 <= count <= blocked + for name, count in blockers.items() + ) + or (blocked == 0) != (not blockers) + or (blocked > 0 and sum(blockers.values()) < blocked) + ): + raise ValueError("campaign evidence blocker vocabulary/shape is invalid") + exact_case_counts = { + "restart_cases_passed": 14, + "replay_cases_passed": 1, + "mutation_cases_passed": 8, + "anti_lookahead_cases_passed": 6, + "concurrency_cases_passed": 10, + "runtime_crash_cases_passed": 9, + "campaign_crash_cases_passed": 10, + } + if any( + type(payload[key]) is not int or payload[key] != value + for key, value in exact_case_counts.items() + ): + raise ValueError("campaign evidence assurance counts differ from exact catalogs") + + actual_migration_boundary = _migration_boundary() + + if any( + payload[key] is not False + for key in ( + "public_network_smoke_executed", + "operator_bootstrap_executed", + "authenticated_proof_executed", + "production_submit_reachable", + "production_cancel_reachable", + "migration_0027_exists", + ) + ): + raise PermissionError("campaign evidence reports forbidden authority") + if payload["real_proof_authorization"] != "NO": + raise PermissionError("real authenticated proof authorization must remain NO") + if any( + type(payload[key]) is not int or payload[key] != 0 + for key in ( + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + ) + ): + raise PermissionError("campaign evidence reports a write/auth/credential call") + if ( + payload["postgresql_campaign_classification"] + != POSTGRESQL_CAMPAIGN_NOT_EXECUTED + or type(payload["postgresql_campaign_event_count"]) is not int + or payload["postgresql_campaign_event_count"] != 0 + ): + raise ValueError( + "checked artifact must truthfully classify PostgreSQL as not executed" + ) + if ( + actual_migration_boundary != (26, "0026", MIGRATION_0026_SHA256, False) + or payload["migration_count"] != actual_migration_boundary[0] + or payload["latest_migration"] != actual_migration_boundary[1] + or payload["migration_0026_sha256"] != actual_migration_boundary[2] + or payload["migration_0027_exists"] != actual_migration_boundary[3] + ): + raise ValueError("campaign evidence migration boundary mismatch") + _scan_public_value(payload) + + +def validate_public_historical_shadow_campaign_evidence( + payload: Mapping[str, object], + *, + verifier_result: Mapping[str, object] | None = None, +) -> None: + """Re-run the verifier and compare every evidence field to executed facts. + + ``verifier_result`` remains an optional expected value for compatibility, but + it can no longer replace verifier execution. + """ + + if type(payload) is not dict: + raise TypeError("campaign evidence must use an exact JSON object") + _validate_exact_json_tree(payload) + repository_value = payload.get("repository_sha", "") + if type(repository_value) is not str: + raise TypeError("campaign evidence repository SHA must be exact text") + repository_sha = validate_git_commit_sha(repository_value) + if verifier_result is not None: + validate_historical_shadow_campaign_verifier_result( + verifier_result, + repository_sha=repository_sha, + ) + executed = run_historical_shadow_campaign_verifier(repository_sha) + if verifier_result is not None: + if verifier_result != executed: + raise ValueError("caller-provided verifier result differs from re-execution") + _validate_public_historical_shadow_campaign_evidence_against_result( + payload, + verifier_result=executed, + ) + + +__all__ = [ + "ACCEPTED_SHADOW_BASELINE_MERGE_SHA", + "CAMPAIGN_PUBLIC_EVIDENCE_KEYS", + "build_public_historical_shadow_campaign_evidence", + "canonical_public_historical_shadow_campaign_evidence_bytes", + "load_public_historical_shadow_campaign_evidence", + "validate_public_historical_shadow_campaign_evidence", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_expected_hashes.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_expected_hashes.py new file mode 100644 index 0000000..741f606 --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_expected_hashes.py @@ -0,0 +1,313 @@ +"""Independent checked hash manifest for the canonical Phase 8B campaign.""" + +from __future__ import annotations + +import hashlib +from pathlib import Path +from types import MappingProxyType +from typing import Mapping + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .shadow_campaign_models import ( + ShadowCampaignCorpusHashes, + _validate_shadow_campaign_corpus_hashes_authority, +) + + +_SOURCE_NORMALIZATION_VERSION = "utf8-bom-stripped-crlf-cr-to-lf-v1" + +_CONCRETE_PATH_TYPE = type(Path()) +EXPECTED_FULL_HASH_MANIFEST_VERSION = "phase8b-shadow-expected-hashes-v1" +EXPECTED_FULL_CAMPAIGN_SPEC_SHA256 = ( + "2cc12c280c9a2c7bf59f118b77dded82498eb736bfa2a4ddaba8a97349ba9c0f" +) +EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256 = ( + "6984f7a02e809aa13e2a3c2b733d111aa6a86babaa9786012637db428b6c3a16" +) +EXPECTED_CORPUS_GENERATOR_SHA256 = ( + "99a930e21c1c0a08e10bc3fe1bab80afdccaf8706de0cb6c2fe3e81a602e4fee" +) +EXPECTED_FULL_EVENT_SEQUENCE_SHA256 = ( + "7a58d2a2984f8722bc3f13cc7f67a1b04d10e11ae21534d7665d2caf6f8eb9a2" +) +EXPECTED_FULL_CORPUS_RESULT_SHA256 = ( + "2f5c235c6cd4dca92d1938791aad73b869bc7183474dcc1937f50fa9e5da1ecc" +) +EXPECTED_COMPACT_EVENT_COUNT = 1_440 +EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256 = ( + "c55054bdd7d604273f1dcdc09b6e56a9219cce16a13266d39585632266392c59" +) +EXPECTED_COMPACT_CORPUS_RESULT_SHA256 = ( + "466933e0cff98515fa94042b94c254e778630a9457b9b2986dd250cb0e2a3b96" +) +EXPECTED_COMPACT_SEGMENT_HASHES = MappingProxyType({}) +EXPECTED_COMPACT_WINDOW_HASHES = MappingProxyType( + { + "1d": "eb3171e97569c582a5dc4e9c39c392f4db8d279032a98a387ba564c4e0747188" + } +) + +EXPECTED_FULL_SEGMENT_HASHES = MappingProxyType( + { + "full-90d-segment-01-quiet-range": ( + "34312c52eb7b722685376009a7a74d147550cce44ba85e768a323014ab00d0dc" + ), + "full-90d-segment-02-low-volatility-uptrend": ( + "431077f03cc9c31a0e5305bf91f4bd0ec0ee4e871972e9bf6a31034a015ef24a" + ), + "full-90d-segment-03-low-volatility-downtrend": ( + "c72358961f6f434bfbfacb3bf4496f25ac00e5f072ffaad53e151df43e61cc04" + ), + "full-90d-segment-04-high-volatility-uptrend": ( + "16f851680db8310f59a893f441ed10a3820015a53d1c75ba7d20a561b42a52b4" + ), + "full-90d-segment-05-high-volatility-downtrend": ( + "362fdf03af4eee6633f53ce9e0abb65e8a93f85134f5536027db0a00a2d76dc9" + ), + "full-90d-segment-06-volatility-spike": ( + "80349e2c3a5c37a30914607f4fe957ed0a927d0e3e079d2c21317b96569327e8" + ), + "full-90d-segment-07-flash-drawdown-and-bounded-rebound": ( + "219412b4ee724d5d0c93326634f833168871dea1767b88dad8b012f3ca75a81d" + ), + "full-90d-segment-08-deterministic-gap-sequence": ( + "4cc780f8ac577fe9a3acc4736c6a3e195937971ee22385158638b321eac13479" + ), + "full-90d-segment-09-illiquid-wide-spread-conditions": ( + "b8c1763af98d80f9aa777cd97f2993bdde1d2002f07aac7ccd2daccd34c51cef" + ), + "full-90d-segment-10-stale-missing-duplicate-out-of-order-sequence": ( + "ff02d27289b3a7756faa654890cfa0944537b3b7b398911034f50d0e9fcd1770" + ), + "full-90d-segment-11-instrument-metadata-boundary-churn": ( + "f6688ab95cb0e08c32c74738c903f2b74423243215cbc1e0f99b1a3ec226541b" + ), + "full-90d-segment-12-recovery-and-normalization": ( + "8f3155743750afc9532ad60837f92bf29fbd664bc685211c2d12bb6e0abb283c" + ), + } +) +EXPECTED_FULL_WINDOW_HASHES = MappingProxyType( + { + "1d": "eb3171e97569c582a5dc4e9c39c392f4db8d279032a98a387ba564c4e0747188", + "7d": "baad30f6ecd49f184a2b68b16a09e9edb758933d454ad136590a50aaf1c429cc", + "30d": "ecf548a22c2ddc125afdc00424a9fce4adb3ffd539cdb6eb167d625dc20d008c", + "90d": "7a58d2a2984f8722bc3f13cc7f67a1b04d10e11ae21534d7665d2caf6f8eb9a2", + } +) +EXPECTED_GENERATOR_SOURCE_SHA256S = MappingProxyType( + { + "shadow_campaign_corpus.py": ( + "897eaf39406b807f3c53cb25ca1285b737b7373adb6393cd593e3f4eef1389ce" + ), + "shadow_campaign_accounts.py": ( + "4ccfcfb9cae77c555f09761e16f302eeb163aa2df59f137b51b2b01e3e15a8cc" + ), + "shadow_campaign_models.py": ( + "8840d336d56176da27eb09aa4fe8fd84ebe558d83cf0f66d3f29945050e89a18" + ), + } +) +EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256 = ( + "eb6161e005beba49abab0413781a59d1ce3491a6e7b4191cda2a67350fe7374f" +) + +_EXPECTED_FULL_HASH_MANIFEST_CORE = { + "manifest_version": EXPECTED_FULL_HASH_MANIFEST_VERSION, + "campaign_spec_sha256": EXPECTED_FULL_CAMPAIGN_SPEC_SHA256, + "synthetic_account_timeline_sha256": ( + EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256 + ), + "corpus_generator_sha256": EXPECTED_CORPUS_GENERATOR_SHA256, + "generator_source_implementation_sha256": ( + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256 + ), + "event_sequence_sha256": EXPECTED_FULL_EVENT_SEQUENCE_SHA256, + "corpus_result_sha256": EXPECTED_FULL_CORPUS_RESULT_SHA256, + "segment_hashes": dict(EXPECTED_FULL_SEGMENT_HASHES), + "window_hashes": dict(EXPECTED_FULL_WINDOW_HASHES), + "compact": { + "event_count": EXPECTED_COMPACT_EVENT_COUNT, + "event_sequence_sha256": EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256, + "corpus_result_sha256": EXPECTED_COMPACT_CORPUS_RESULT_SHA256, + "segment_hashes": dict(EXPECTED_COMPACT_SEGMENT_HASHES), + "window_hashes": dict(EXPECTED_COMPACT_WINDOW_HASHES), + }, + "generator_source_sha256s": dict(EXPECTED_GENERATOR_SOURCE_SHA256S), +} +EXPECTED_FULL_HASH_MANIFEST_SHA256 = ( + "bef3093164d30263251932714811c1519706fd20e0107f138d32c8d3f8c0f390" +) + + +def normalized_source_sha256(source: bytes) -> str: + """Hash UTF-8 source after deterministic cross-platform line normalization.""" + + if type(source) is not bytes: + raise TypeError("generator source must be exact bytes") + if source.startswith(b"\xef\xbb\xbf"): + source = source[3:] + normalized = source.replace(b"\r\n", b"\n").replace(b"\r", b"\n") + normalized.decode("utf-8", errors="strict") + return hashlib.sha256(normalized).hexdigest() + + +def calculate_generator_source_sha256s( + source_directory: str | Path | None = None, +) -> Mapping[str, str]: + """Read only the three repository-owned generator inputs and hash their source.""" + + if source_directory is None: + root = Path(__file__).resolve().parent + else: + if type(source_directory) not in (str, _CONCRETE_PATH_TYPE): + raise TypeError( + "generator source directory must be exact text or an exact Path" + ) + root = Path(source_directory) + observed = { + name: normalized_source_sha256((root / name).read_bytes()) + for name in EXPECTED_GENERATOR_SOURCE_SHA256S + } + return MappingProxyType(observed) + + +def _generator_source_implementation_sha256( + source_sha256s: Mapping[str, str], +) -> str: + return sha256_payload( + { + "operation": "phase8b-shadow-generator-source-implementation-v1", + "normalization": _SOURCE_NORMALIZATION_VERSION, + "source_sha256s": dict(source_sha256s), + } + ) + + +def calculate_generator_source_implementation_sha256( + source_directory: str | Path | None = None, +) -> str: + """Return the real normalized implementation hash from current source bytes.""" + + return _generator_source_implementation_sha256( + calculate_generator_source_sha256s(source_directory) + ) + + +def validate_generator_source_implementation( + source_directory: str | Path | None = None, +) -> str: + """Fail closed if current generator source differs from the committed manifest.""" + + observed = calculate_generator_source_sha256s(source_directory) + if dict(observed) != dict(EXPECTED_GENERATOR_SOURCE_SHA256S): + raise ValueError("historical campaign generator source manifest mismatch") + implementation_sha256 = _generator_source_implementation_sha256(observed) + if implementation_sha256 != EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256: + raise ValueError("historical campaign generator implementation hash mismatch") + return implementation_sha256 + + +def validate_expected_hash_manifest() -> str: + """Validate the independently committed checksum of all expected corpus facts.""" + + observed = sha256_payload(_EXPECTED_FULL_HASH_MANIFEST_CORE) + if observed != EXPECTED_FULL_HASH_MANIFEST_SHA256: + raise ValueError("historical campaign expected hash manifest is inconsistent") + return observed + + +def validate_expected_corpus_results( + *, + campaign_spec_sha256: str, + synthetic_account_timeline_sha256: str, + corpus_generator_sha256: str, + full_result: object, + compact_result: object, +) -> str: + """Validate executed full and compact corpus results against fixed expectations.""" + + for name, value in ( + ("campaign_spec_sha256", campaign_spec_sha256), + ("synthetic_account_timeline_sha256", synthetic_account_timeline_sha256), + ("corpus_generator_sha256", corpus_generator_sha256), + ): + if ( + type(value) is not str + or len(value) != 64 + or any(character not in "0123456789abcdef" for character in value) + ): + raise TypeError(f"{name} must be an exact lowercase SHA-256 digest") + _validate_shadow_campaign_corpus_hashes_authority(full_result) + _validate_shadow_campaign_corpus_hashes_authority(compact_result) + + validate_expected_hash_manifest() + if ( + campaign_spec_sha256 != EXPECTED_FULL_CAMPAIGN_SPEC_SHA256 + or synthetic_account_timeline_sha256 + != EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256 + or corpus_generator_sha256 != EXPECTED_CORPUS_GENERATOR_SHA256 + ): + raise ValueError("historical campaign fixed identity manifest mismatch") + full_observed = { + "event_count": full_result.event_count, + "event_sequence_sha256": full_result.event_sequence_sha256, + "corpus_result_sha256": full_result.campaign_result_sha256, + "segment_hashes": dict(full_result.segment_hashes), + "window_hashes": dict(full_result.window_hashes), + "complete": full_result.complete, + } + full_expected = { + "event_count": 25_920, + "event_sequence_sha256": EXPECTED_FULL_EVENT_SEQUENCE_SHA256, + "corpus_result_sha256": EXPECTED_FULL_CORPUS_RESULT_SHA256, + "segment_hashes": dict(EXPECTED_FULL_SEGMENT_HASHES), + "window_hashes": dict(EXPECTED_FULL_WINDOW_HASHES), + "complete": True, + } + compact_observed = { + "event_count": compact_result.event_count, + "event_sequence_sha256": compact_result.event_sequence_sha256, + "corpus_result_sha256": compact_result.campaign_result_sha256, + "segment_hashes": dict(compact_result.segment_hashes), + "window_hashes": dict(compact_result.window_hashes), + "complete": compact_result.complete, + } + compact_expected = { + "event_count": EXPECTED_COMPACT_EVENT_COUNT, + "event_sequence_sha256": EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256, + "corpus_result_sha256": EXPECTED_COMPACT_CORPUS_RESULT_SHA256, + "segment_hashes": dict(EXPECTED_COMPACT_SEGMENT_HASHES), + "window_hashes": dict(EXPECTED_COMPACT_WINDOW_HASHES), + "complete": False, + } + if full_observed != full_expected or compact_observed != compact_expected: + raise ValueError("executed historical campaign corpus differs from manifest") + return EXPECTED_FULL_HASH_MANIFEST_SHA256 + + +__all__ = [ + "EXPECTED_COMPACT_CORPUS_RESULT_SHA256", + "EXPECTED_COMPACT_EVENT_COUNT", + "EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256", + "EXPECTED_COMPACT_SEGMENT_HASHES", + "EXPECTED_COMPACT_WINDOW_HASHES", + "EXPECTED_CORPUS_GENERATOR_SHA256", + "EXPECTED_FULL_CAMPAIGN_SPEC_SHA256", + "EXPECTED_FULL_CORPUS_RESULT_SHA256", + "EXPECTED_FULL_EVENT_SEQUENCE_SHA256", + "EXPECTED_FULL_HASH_MANIFEST_SHA256", + "EXPECTED_FULL_HASH_MANIFEST_VERSION", + "EXPECTED_FULL_SEGMENT_HASHES", + "EXPECTED_FULL_WINDOW_HASHES", + "EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256", + "EXPECTED_GENERATOR_SOURCE_SHA256S", + "EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256", + "calculate_generator_source_implementation_sha256", + "calculate_generator_source_sha256s", + "normalized_source_sha256", + "validate_expected_corpus_results", + "validate_expected_hash_manifest", + "validate_generator_source_implementation", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_models.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_models.py new file mode 100644 index 0000000..229c4aa --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_models.py @@ -0,0 +1,864 @@ +"""Immutable public-safe contracts for the historical shadow campaign corpus. + +The contracts in this module carry generated public-market and explicitly synthetic +account inputs only. They intentionally expose no transport, credential, broker, +submit, cancel, or persistence authority. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from decimal import Decimal +from types import MappingProxyType +from typing import Mapping + +from secure_eval_wrapper.data_collection.hashing import sha256_payload +from secure_eval_wrapper.data_collection.time_utils import require_utc_datetime + + +def _text(value: str, name: str) -> str: + if type(value) is not str or not value.strip(): + raise ValueError(f"{name} must be non-empty text") + return value.strip() + + +def _digest(value: str, name: str) -> str: + if ( + type(value) is not str + or len(value) != 64 + or any(character not in "0123456789abcdef" for character in value) + ): + raise ValueError(f"{name} must be a lowercase SHA-256 digest") + return value + + +def _integer(value: int, name: str, *, positive: bool = False) -> int: + if type(value) is not int: + raise ValueError(f"{name} must be an integer") + if positive and value <= 0: + raise ValueError(f"{name} must be positive") + if not positive and value < 0: + raise ValueError(f"{name} must be non-negative") + return value + + +def _decimal( + value: Decimal, + name: str, + *, + positive: bool = False, + nonnegative: bool = False, +) -> Decimal: + if type(value) is not Decimal or not value.is_finite(): + raise ValueError(f"{name} must be a finite Decimal") + if positive and value <= 0: + raise ValueError(f"{name} must be positive") + if nonnegative and value < 0: + raise ValueError(f"{name} must be non-negative") + return value + + +_MAPPING_PROXY_TYPE = type(MappingProxyType({})) +_SEALED_CANONICAL_MAPPINGS: dict[int, Mapping[str, object]] = {} + + +def _sealed_mapping(values: dict[str, object]) -> Mapping[str, object]: + proxy = MappingProxyType(values) + _SEALED_CANONICAL_MAPPINGS[id(proxy)] = proxy + return proxy + + +def _is_sealed_canonical_mapping(value: object) -> bool: + return ( + type(value) is _MAPPING_PROXY_TYPE + and _SEALED_CANONICAL_MAPPINGS.get(id(value)) is value + ) + + +def _utc_datetime(value: datetime, name: str) -> datetime: + if type(value) is not datetime: + raise ValueError(f"{name} must be an exact datetime") + return require_utc_datetime(value, field_name=name) + + +def _freeze(value: object) -> object: + """Recursively freeze one canonical public payload.""" + + if type(value) is _MAPPING_PROXY_TYPE: + if not _is_sealed_canonical_mapping(value): + raise ValueError("canonical mapping proxy was not sealed by this module") + return value + if type(value) is dict: + if any(type(key) is not str for key in value): + raise ValueError("canonical mapping keys must be strings") + return _sealed_mapping( + {key: _freeze(value[key]) for key in sorted(value)} + ) + if type(value) in (list, tuple): + return tuple(_freeze(item) for item in value) + if type(value) in (str, int, bool, Decimal, datetime) or value is None: + return value + raise ValueError(f"unsupported canonical payload value: {type(value).__name__}") + + +def _contains_key(value: object, forbidden: frozenset[str]) -> bool: + if type(value) is _MAPPING_PROXY_TYPE: + if not _is_sealed_canonical_mapping(value): + raise ValueError("canonical mapping proxy was not sealed by this module") + return any( + key.lower() in forbidden or _contains_key(item, forbidden) + for key, item in value.items() + ) + if type(value) in (tuple, list): + return any(_contains_key(item, forbidden) for item in value) + return False + + +def campaign_event_id( + *, + event_index: int, +) -> str: + """Derive the lineage-independent identity of one logical event position.""" + + event_index = _integer(event_index, "event_index") + identity = sha256_payload( + { + "operation": "phase8b-shadow-campaign-logical-event-id-v1", + "event_index": event_index, + } + ) + return f"shadow-campaign-event-{event_index:05d}-{identity[:16]}" + + +@dataclass(frozen=True, slots=True) +class ShadowCampaignSegment: + """One immutable generated-corpus regime specification.""" + + segment_id: str + segment_index: int + regime: str + start_event_index: int + event_count: int + seed: int + generator_parameters: Mapping[str, object] + + def __post_init__(self) -> None: + object.__setattr__(self, "segment_id", _text(self.segment_id, "segment_id")) + object.__setattr__(self, "regime", _text(self.regime, "regime").lower()) + _integer(self.segment_index, "segment_index") + _integer(self.start_event_index, "start_event_index") + _integer(self.event_count, "event_count", positive=True) + _integer(self.seed, "seed") + object.__setattr__( + self, + "generator_parameters", + _freeze(self.generator_parameters), + ) + + @property + def start_index(self) -> int: + """Compatibility alias used by campaign orchestration.""" + + return self.start_event_index + + @property + def end_event_index_exclusive(self) -> int: + return self.start_event_index + self.event_count + + @property + def canonical_payload(self) -> Mapping[str, object]: + return MappingProxyType( + { + "segment_id": self.segment_id, + "segment_index": self.segment_index, + "regime": self.regime, + "start_event_index": self.start_event_index, + "event_count": self.event_count, + "seed": self.seed, + "generator_parameters": self.generator_parameters, + } + ) + + @property + def segment_sha256(self) -> str: + return sha256_payload(self.canonical_payload) + + @property + def canonical_hash(self) -> str: + return self.segment_sha256 + + +def _validate_shadow_campaign_segment_authority( + segment: ShadowCampaignSegment, +) -> None: + """Validate exact slot/container types without invoking injected behavior.""" + + if type(segment) is not ShadowCampaignSegment: + raise TypeError("segment must use the exact ShadowCampaignSegment type") + for name in ("segment_id", "regime"): + if type(object.__getattribute__(segment, name)) is not str: + raise TypeError(f"segment {name} must be exact text") + for name in ("segment_index", "start_event_index", "event_count", "seed"): + if type(object.__getattribute__(segment, name)) is not int: + raise TypeError(f"segment {name} must be an exact integer") + parameters = object.__getattribute__(segment, "generator_parameters") + if not _is_sealed_canonical_mapping(parameters): + raise TypeError( + "segment generator_parameters must be a sealed canonical mapping" + ) + + +@dataclass(frozen=True, slots=True) +class ShadowCampaignSpec: + """Complete deterministic campaign specification, not a runtime checkpoint.""" + + campaign_id: str + campaign_version: str + corpus_generator_version: str + corpus_generator_sha256: str + instrument: str + instrument_type: str + timeframe: str + start_at_utc: datetime + event_count: int + events_per_segment: int + equivalent_duration_days: int + segments: tuple[ShadowCampaignSegment, ...] + window_labels: tuple[str, ...] + window_event_counts: tuple[int, ...] + repository_owned: bool = True + network_access_allowed: bool = False + credential_access_allowed: bool = False + user_file_access_allowed: bool = False + + def __post_init__(self) -> None: + object.__setattr__(self, "campaign_id", _text(self.campaign_id, "campaign_id")) + object.__setattr__( + self, "campaign_version", _text(self.campaign_version, "campaign_version") + ) + object.__setattr__( + self, + "corpus_generator_version", + _text(self.corpus_generator_version, "corpus_generator_version"), + ) + _digest(self.corpus_generator_sha256, "corpus_generator_sha256") + object.__setattr__(self, "instrument", _text(self.instrument, "instrument").upper()) + object.__setattr__( + self, "instrument_type", _text(self.instrument_type, "instrument_type").lower() + ) + object.__setattr__(self, "timeframe", _text(self.timeframe, "timeframe").lower()) + object.__setattr__( + self, + "start_at_utc", + _utc_datetime(self.start_at_utc, "start_at_utc"), + ) + _integer(self.event_count, "event_count", positive=True) + _integer(self.events_per_segment, "events_per_segment", positive=True) + _integer(self.equivalent_duration_days, "equivalent_duration_days", positive=True) + if (self.instrument, self.instrument_type, self.timeframe) != ( + "BTC-USDT", + "spot", + "5m", + ): + raise ValueError("the historical shadow corpus is fixed to BTC-USDT Spot 5m") + + if type(self.segments) is not tuple: + raise ValueError("segments must use an exact tuple") + segments = self.segments + if not segments or any( + type(item) is not ShadowCampaignSegment for item in segments + ): + raise ValueError("segments must contain exact ShadowCampaignSegment values") + for segment in segments: + _validate_shadow_campaign_segment_authority(segment) + expected_start = 0 + for expected_index, segment in enumerate(segments): + if segment.segment_index != expected_index: + raise ValueError("segment indexes must be contiguous and zero-based") + if segment.start_event_index != expected_start: + raise ValueError("segment event ranges must be contiguous") + if segment.event_count != self.events_per_segment: + raise ValueError("every segment must use events_per_segment") + expected_start = segment.end_event_index_exclusive + if expected_start != self.event_count: + raise ValueError("segment ranges must cover the exact campaign event count") + if len({item.segment_id for item in segments}) != len(segments): + raise ValueError("segment IDs must be unique") + object.__setattr__(self, "segments", segments) + + if type(self.window_labels) is not tuple or type( + self.window_event_counts + ) is not tuple: + raise ValueError("window labels and counts must use exact tuples") + labels = tuple(_text(item, "window label").lower() for item in self.window_labels) + counts = self.window_event_counts + if not labels or len(labels) != len(counts) or len(set(labels)) != len(labels): + raise ValueError("window labels and counts must be unique aligned tuples") + for count in counts: + _integer(count, "window event count", positive=True) + if count > self.event_count: + raise ValueError("window event count exceeds the campaign") + if tuple(sorted(counts)) != counts or len(set(counts)) != len(counts): + raise ValueError("window event counts must be strictly increasing") + object.__setattr__(self, "window_labels", labels) + object.__setattr__(self, "window_event_counts", counts) + + if ( + self.repository_owned is not True + or self.network_access_allowed is not False + or self.credential_access_allowed is not False + or self.user_file_access_allowed is not False + ): + raise PermissionError("campaign corpus safety flags are immutable") + + @property + def segment_count(self) -> int: + return len(self.segments) + + @property + def fixed_seeds(self) -> tuple[int, ...]: + return tuple(segment.seed for segment in self.segments) + + @property + def windows(self) -> Mapping[str, int]: + return MappingProxyType(dict(zip(self.window_labels, self.window_event_counts))) + + @property + def canonical_payload(self) -> Mapping[str, object]: + return MappingProxyType( + { + "campaign_id": self.campaign_id, + "campaign_version": self.campaign_version, + "corpus_generator_version": self.corpus_generator_version, + "corpus_generator_sha256": self.corpus_generator_sha256, + "instrument": self.instrument, + "instrument_type": self.instrument_type, + "timeframe": self.timeframe, + "start_at_utc": self.start_at_utc, + "event_count": self.event_count, + "events_per_segment": self.events_per_segment, + "equivalent_duration_days": self.equivalent_duration_days, + "segments": tuple(segment.canonical_payload for segment in self.segments), + "window_labels": self.window_labels, + "window_event_counts": self.window_event_counts, + "repository_owned": self.repository_owned, + "network_access_allowed": self.network_access_allowed, + "credential_access_allowed": self.credential_access_allowed, + "user_file_access_allowed": self.user_file_access_allowed, + } + ) + + @property + def spec_sha256(self) -> str: + return sha256_payload(self.canonical_payload) + + @property + def canonical_hash(self) -> str: + return self.spec_sha256 + + +@dataclass(frozen=True, slots=True) +class ShadowCampaignEvent: + """One generated market event and its public decision-input projection.""" + + campaign_id: str + campaign_spec_sha256: str + corpus_generator_version: str + corpus_generator_sha256: str + event_index: int + event_id: str + segment_id: str + segment_index: int + segment_event_index: int + scheduled_at_utc: datetime + market_timestamp_utc: datetime + provider: str + instrument: str + instrument_type: str + timeframe: str + open_price: Decimal + high_price: Decimal + low_price: Decimal + close_price: Decimal + bid_price: Decimal + ask_price: Decimal + volume: Decimal + instrument_status: str + settlement_asset: str + tick_size: Decimal + lot_size: Decimal + minimum_quantity: Decimal + maximum_quantity: Decimal + direction: str + quantity: Decimal + limit_price: Decimal + order_type: str + quality_flags: tuple[str, ...] + + def __post_init__(self) -> None: + object.__setattr__(self, "campaign_id", _text(self.campaign_id, "campaign_id")) + _digest(self.campaign_spec_sha256, "campaign_spec_sha256") + object.__setattr__( + self, + "corpus_generator_version", + _text(self.corpus_generator_version, "corpus_generator_version"), + ) + _digest(self.corpus_generator_sha256, "corpus_generator_sha256") + _integer(self.event_index, "event_index") + object.__setattr__(self, "event_id", _text(self.event_id, "event_id")) + object.__setattr__(self, "segment_id", _text(self.segment_id, "segment_id")) + _integer(self.segment_index, "segment_index") + _integer(self.segment_event_index, "segment_event_index") + object.__setattr__( + self, + "scheduled_at_utc", + _utc_datetime(self.scheduled_at_utc, "scheduled_at_utc"), + ) + object.__setattr__( + self, + "market_timestamp_utc", + _utc_datetime(self.market_timestamp_utc, "market_timestamp_utc"), + ) + if self.market_timestamp_utc > self.scheduled_at_utc: + raise ValueError("market_timestamp_utc cannot be later than scheduled_at_utc") + object.__setattr__(self, "provider", _text(self.provider, "provider").lower()) + object.__setattr__(self, "instrument", _text(self.instrument, "instrument").upper()) + object.__setattr__( + self, "instrument_type", _text(self.instrument_type, "instrument_type").lower() + ) + object.__setattr__(self, "timeframe", _text(self.timeframe, "timeframe").lower()) + object.__setattr__( + self, "instrument_status", _text(self.instrument_status, "instrument_status").lower() + ) + object.__setattr__( + self, "settlement_asset", _text(self.settlement_asset, "settlement_asset").upper() + ) + object.__setattr__(self, "direction", _text(self.direction, "direction").lower()) + object.__setattr__(self, "order_type", _text(self.order_type, "order_type").lower()) + if self.direction not in {"long", "short"}: + raise ValueError("campaign direction must be long or short") + if self.order_type != "limit": + raise ValueError("campaign decision input supports limit orders only") + if (self.instrument, self.instrument_type, self.timeframe) != ( + "BTC-USDT", + "spot", + "5m", + ): + raise ValueError("campaign events are fixed to BTC-USDT Spot 5m") + + for name in ( + "open_price", + "high_price", + "low_price", + "close_price", + "bid_price", + "ask_price", + "tick_size", + "lot_size", + "minimum_quantity", + "maximum_quantity", + "quantity", + "limit_price", + ): + _decimal(getattr(self, name), name, positive=True) + _decimal(self.volume, "volume", nonnegative=True) + if self.high_price < max(self.open_price, self.close_price): + raise ValueError("high_price is below the event open/close") + if self.low_price > min(self.open_price, self.close_price): + raise ValueError("low_price is above the event open/close") + if self.bid_price > self.ask_price: + raise ValueError("campaign bid cannot exceed ask") + if self.minimum_quantity > self.maximum_quantity: + raise ValueError("instrument quantity bounds are inverted") + if not self.minimum_quantity <= self.quantity <= self.maximum_quantity: + raise ValueError("campaign quantity is outside instrument bounds") + + if type(self.quality_flags) is not tuple: + raise ValueError("quality_flags must use an exact tuple") + flags = tuple(sorted({_text(item, "quality flag").lower() for item in self.quality_flags})) + object.__setattr__(self, "quality_flags", flags) + expected_id = campaign_event_id(event_index=self.event_index) + if self.event_id != expected_id: + raise ValueError("event_id does not match the deterministic event identity") + + @property + def last_price(self) -> Decimal: + return self.close_price + + @property + def runtime_payload(self) -> Mapping[str, object]: + """Decision inputs only; regime and expected-result metadata are excluded.""" + + return MappingProxyType( + { + "provider": self.provider, + "instrument": self.instrument, + "instrument_type": self.instrument_type, + "timeframe": self.timeframe, + "bid": self.bid_price, + "ask": self.ask_price, + "last_price": self.last_price, + "public_timestamp_utc": self.market_timestamp_utc, + "decision_at_utc": self.scheduled_at_utc, + "instrument_status": self.instrument_status, + "settlement_asset": self.settlement_asset, + "tick_size": self.tick_size, + "lot_size": self.lot_size, + "minimum_quantity": self.minimum_quantity, + "maximum_quantity": self.maximum_quantity, + "direction": self.direction, + "quantity": self.quantity, + "limit_price": self.limit_price, + "order_type": self.order_type, + "quality_flags": self.quality_flags, + } + ) + + @property + def canonical_payload(self) -> Mapping[str, object]: + """Canonical point-in-time data identity, independent of campaign lineage.""" + + return MappingProxyType( + { + "operation": "phase8b-shadow-campaign-data-event-v1", + "event_index": self.event_index, + "event_id": self.event_id, + "scheduled_at_utc": self.scheduled_at_utc, + "market_timestamp_utc": self.market_timestamp_utc, + "open_price": self.open_price, + "high_price": self.high_price, + "low_price": self.low_price, + "close_price": self.close_price, + "volume": self.volume, + "runtime_payload": self.runtime_payload, + } + ) + + @property + def event_sha256(self) -> str: + return sha256_payload(self.canonical_payload) + + @property + def event_hash(self) -> str: + return self.event_sha256 + + @property + def canonical_hash(self) -> str: + return self.event_sha256 + + +def _validate_shadow_campaign_event_authority( + event: ShadowCampaignEvent, +) -> None: + """Validate event slot types before any generated-event property is read.""" + + if type(event) is not ShadowCampaignEvent: + raise TypeError("event must use the exact ShadowCampaignEvent type") + for name in ( + "campaign_id", + "campaign_spec_sha256", + "corpus_generator_version", + "corpus_generator_sha256", + "event_id", + "segment_id", + "provider", + "instrument", + "instrument_type", + "timeframe", + "instrument_status", + "settlement_asset", + "direction", + "order_type", + ): + if type(object.__getattribute__(event, name)) is not str: + raise TypeError(f"event {name} must be exact text") + for name in ("event_index", "segment_index", "segment_event_index"): + if type(object.__getattribute__(event, name)) is not int: + raise TypeError(f"event {name} must be an exact integer") + for name in ("scheduled_at_utc", "market_timestamp_utc"): + if type(object.__getattribute__(event, name)) is not datetime: + raise TypeError(f"event {name} must be an exact datetime") + for name in ( + "open_price", "high_price", "low_price", "close_price", + "bid_price", "ask_price", "volume", "tick_size", "lot_size", + "minimum_quantity", "maximum_quantity", "quantity", "limit_price", + ): + if type(object.__getattribute__(event, name)) is not Decimal: + raise TypeError(f"event {name} must be an exact Decimal") + flags = object.__getattribute__(event, "quality_flags") + if type(flags) is not tuple or any(type(item) is not str for item in flags): + raise TypeError("event quality_flags must be an exact tuple of exact text") + + +def synthetic_timeline_snapshot_id( + *, + state_name: str, + effective_start_event_index: int, + effective_end_event_index: int, + runtime_payload: Mapping[str, object], +) -> str: + state_name = _text(state_name, "state_name").lower() + effective_start_event_index = _integer( + effective_start_event_index, "effective_start_event_index" + ) + effective_end_event_index = _integer( + effective_end_event_index, "effective_end_event_index", positive=True + ) + if effective_end_event_index <= effective_start_event_index: + raise ValueError("synthetic snapshot effective range is empty") + frozen_payload = _freeze(runtime_payload) + if type(frozen_payload) is not _MAPPING_PROXY_TYPE: + raise ValueError("runtime_payload must be an exact canonical mapping") + digest = sha256_payload( + { + "state_name": state_name, + "effective_start_event_index": effective_start_event_index, + "effective_end_event_index": effective_end_event_index, + "runtime_payload_sha256": sha256_payload(frozen_payload), + } + ) + return f"synthetic-account-{effective_start_event_index:05d}-{digest[:16]}" + + +@dataclass(frozen=True, slots=True) +class SyntheticAccountTimelineSnapshot: + """Synthetic risk input whose expectation metadata cannot enter runtime hashes.""" + + snapshot_id: str + state_name: str + effective_start_event_index: int + effective_end_event_index: int + runtime_payload: Mapping[str, object] + expected_account_blockers: tuple[str, ...] + synthetic_account: bool = True + + def __post_init__(self) -> None: + object.__setattr__(self, "snapshot_id", _text(self.snapshot_id, "snapshot_id")) + object.__setattr__(self, "state_name", _text(self.state_name, "state_name").lower()) + _integer(self.effective_start_event_index, "effective_start_event_index") + _integer( + self.effective_end_event_index, + "effective_end_event_index", + positive=True, + ) + if self.effective_end_event_index <= self.effective_start_event_index: + raise ValueError("synthetic snapshot effective range is empty") + if self.synthetic_account is not True: + raise PermissionError("campaign account snapshots must be explicitly synthetic") + + frozen_payload = _freeze(self.runtime_payload) + if not _is_sealed_canonical_mapping(frozen_payload): + raise ValueError("runtime_payload must be a mapping") + if frozen_payload.get("synthetic_account") is not True: + raise PermissionError("runtime_payload must classify the account as synthetic") + classification = frozen_payload.get("account_classification") + if type(classification) is not str or not classification.startswith("synthetic_"): + raise PermissionError("runtime account classification must be synthetic") + forbidden_expectation_keys = frozenset( + { + "expected_blockers", + "expected_account_blockers", + "expected_result", + "regime", + "segment_label", + } + ) + if _contains_key(frozen_payload, forbidden_expectation_keys): + raise PermissionError("expectation metadata cannot enter runtime_payload") + object.__setattr__(self, "runtime_payload", frozen_payload) + if type(self.expected_account_blockers) is not tuple: + raise ValueError( + "expected_account_blockers must use an exact tuple" + ) + blockers = tuple( + sorted( + { + _text(item, "expected account blocker").lower() + for item in self.expected_account_blockers + } + ) + ) + object.__setattr__(self, "expected_account_blockers", blockers) + expected_id = synthetic_timeline_snapshot_id( + state_name=self.state_name, + effective_start_event_index=self.effective_start_event_index, + effective_end_event_index=self.effective_end_event_index, + runtime_payload=self.runtime_payload, + ) + if self.snapshot_id != expected_id: + raise ValueError("snapshot_id does not match deterministic runtime identity") + + @property + def runtime_payload_sha256(self) -> str: + return sha256_payload(self.runtime_payload) + + @property + def expected_metadata_sha256(self) -> str: + return sha256_payload( + { + "snapshot_id": self.snapshot_id, + "expected_account_blockers": self.expected_account_blockers, + } + ) + + @property + def snapshot_sha256(self) -> str: + """Runtime account hash; deliberately excludes expected blocker metadata.""" + + return sha256_payload( + { + "snapshot_id": self.snapshot_id, + "state_name": self.state_name, + "effective_start_event_index": self.effective_start_event_index, + "effective_end_event_index": self.effective_end_event_index, + "runtime_payload_sha256": self.runtime_payload_sha256, + "synthetic_account": self.synthetic_account, + } + ) + + @property + def canonical_hash(self) -> str: + return self.snapshot_sha256 + + @property + def timeline_entry_sha256(self) -> str: + return sha256_payload( + { + "snapshot_sha256": self.snapshot_sha256, + "expected_metadata_sha256": self.expected_metadata_sha256, + } + ) + + +def _validate_synthetic_account_snapshot_authority( + snapshot: SyntheticAccountTimelineSnapshot, +) -> None: + """Reject a forged exact snapshot before any comparison or payload traversal.""" + + if type(snapshot) is not SyntheticAccountTimelineSnapshot: + raise TypeError( + "snapshot must use the exact SyntheticAccountTimelineSnapshot type" + ) + for name in ("snapshot_id", "state_name"): + if type(object.__getattribute__(snapshot, name)) is not str: + raise TypeError(f"snapshot {name} must be exact text") + for name in ( + "effective_start_event_index", + "effective_end_event_index", + ): + if type(object.__getattribute__(snapshot, name)) is not int: + raise TypeError(f"snapshot {name} must be an exact integer") + payload = object.__getattribute__(snapshot, "runtime_payload") + if not _is_sealed_canonical_mapping(payload): + raise TypeError("snapshot runtime_payload must be a sealed canonical mapping") + blockers = object.__getattribute__(snapshot, "expected_account_blockers") + if type(blockers) is not tuple or any(type(item) is not str for item in blockers): + raise TypeError("snapshot blockers must be an exact tuple of exact text") + if object.__getattribute__(snapshot, "synthetic_account") is not True: + raise PermissionError("snapshot must remain explicitly synthetic") + + +# Explicit campaign-prefixed name for callers that want to avoid the existing +# one-scenario SyntheticAccountSnapshot contract in shadow_models.py. +ShadowCampaignSyntheticAccountSnapshot = SyntheticAccountTimelineSnapshot + + +@dataclass(frozen=True, slots=True) +class ShadowCampaignCorpusHashes: + """Bounded aggregate hashes produced by one streaming corpus traversal.""" + + event_count: int + event_sequence_sha256: str + segment_result_hashes: tuple[tuple[str, str], ...] + window_result_hashes: tuple[tuple[str, str], ...] + campaign_result_sha256: str + complete: bool + + def __post_init__(self) -> None: + _integer(self.event_count, "event_count", positive=True) + _digest(self.event_sequence_sha256, "event_sequence_sha256") + _digest(self.campaign_result_sha256, "campaign_result_sha256") + for name, values in ( + ("segment_result_hashes", self.segment_result_hashes), + ("window_result_hashes", self.window_result_hashes), + ): + if type(values) is not tuple or any( + type(item) is not tuple + or len(item) != 2 + or type(item[0]) is not str + or type(item[1]) is not str + for item in values + ): + raise ValueError( + f"{name} must contain exact text pairs in an exact tuple" + ) + normalized = tuple( + (_text(key, f"{name} key"), _digest(value, f"{name} value")) + for key, value in values + ) + if len({key for key, _ in normalized}) != len(normalized): + raise ValueError(f"{name} keys must be unique") + object.__setattr__(self, name, normalized) + if type(self.complete) is not bool: + raise ValueError("complete must be a boolean") + + @property + def segment_hashes(self) -> Mapping[str, str]: + return MappingProxyType(dict(self.segment_result_hashes)) + + @property + def window_hashes(self) -> Mapping[str, str]: + return MappingProxyType(dict(self.window_result_hashes)) + + def as_dict(self) -> Mapping[str, object]: + return MappingProxyType( + { + "event_count": self.event_count, + "event_sequence_sha256": self.event_sequence_sha256, + "segment_result_hashes": self.segment_hashes, + "window_result_hashes": self.window_hashes, + "campaign_result_sha256": self.campaign_result_sha256, + "complete": self.complete, + } + ) + + +def _validate_shadow_campaign_corpus_hashes_authority( + value: ShadowCampaignCorpusHashes, +) -> None: + """Validate aggregate result slots before properties or comparisons are used.""" + + if type(value) is not ShadowCampaignCorpusHashes: + raise TypeError( + "corpus result must use the exact ShadowCampaignCorpusHashes type" + ) + if type(object.__getattribute__(value, "event_count")) is not int: + raise TypeError("corpus result event_count must be an exact integer") + for name in ("event_sequence_sha256", "campaign_result_sha256"): + if type(object.__getattribute__(value, name)) is not str: + raise TypeError(f"corpus result {name} must be exact text") + for name in ("segment_result_hashes", "window_result_hashes"): + pairs = object.__getattribute__(value, name) + if type(pairs) is not tuple or any( + type(item) is not tuple + or len(item) != 2 + or type(item[0]) is not str + or type(item[1]) is not str + for item in pairs + ): + raise TypeError(f"corpus result {name} must contain exact text pairs") + if type(object.__getattribute__(value, "complete")) is not bool: + raise TypeError("corpus result complete must be an exact boolean") + + +__all__ = [ + "ShadowCampaignCorpusHashes", + "ShadowCampaignEvent", + "ShadowCampaignSegment", + "ShadowCampaignSpec", + "ShadowCampaignSyntheticAccountSnapshot", + "SyntheticAccountTimelineSnapshot", + "campaign_event_id", + "synthetic_timeline_snapshot_id", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_runtime.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_runtime.py new file mode 100644 index 0000000..ec1d271 --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_runtime.py @@ -0,0 +1,1991 @@ +"""Streaming orchestration for the deterministic historical shadow campaign. + +The campaign deliberately reuses the accepted one-event shadow bundle. It does +not persist an aggregate row and it has no transport, credential, broker, submit, +cancel, account-mutation, or fill dependency. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from types import MappingProxyType +from typing import Mapping +from uuid import UUID + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .identity import RuntimeRepositoryIdentity, validate_git_commit_sha +from .shadow_bundle import validate_shadow_bundle_payload +from .shadow_campaign_accounts import ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, + scenario_account_payload, + synthetic_account_snapshot_for_event, + synthetic_account_timeline_sha256, +) +from .shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + _validate_campaign_spec_authority, + iter_shadow_campaign_events, +) +from .shadow_campaign_models import ( + ShadowCampaignEvent, + ShadowCampaignSpec, + SyntheticAccountTimelineSnapshot, + _validate_shadow_campaign_event_authority, + _validate_synthetic_account_snapshot_authority, +) +from .shadow_models import ShadowSafetyFacts, shadow_uuid +from .shadow_repository import ( + MemoryShadowRepository, + PostgresShadowRepository, + ShadowCampaignPersistenceConflict, + ShadowCampaignRepositoryCorruption, + ShadowInjectedCrash, + ShadowPersistenceConflict, + ShadowPostCommitCrash, + shadow_campaign_event_index_from_id, + shadow_campaign_scenario_prefix, + validate_shadow_campaign_database_name, +) +from .shadow_runtime import ( + FixtureShadowMarketSource, + GeneratedShadowScenarioSpec, + RUNTIME_CRASH_POINTS, + ShadowAssuranceRuntime, + ShadowAuthorityError, +) + + +SHADOW_CAMPAIGN_RUNTIME_VERSION = "phase8b-historical-shadow-campaign-runtime-v1" +SHADOW_CAMPAIGN_CHAIN_VERSION = ( + "phase8b-historical-shadow-authority-chain-v1" +) +SHADOW_CAMPAIGN_POLICY_CHAIN_VERSION = "phase8b-historical-shadow-policy-chain-v2" +CAMPAIGN_CRASH_POINTS = ( + "before_corpus_initialization", + "after_campaign_spec_validation", + "before_first_event", + "after_event_bundle_commit", + "before_checkpoint_calculation", + "after_checkpoint_calculation", + "before_segment_completion", + "after_segment_completion", + "before_final_campaign_hash", + "after_final_hash_before_cli_response", +) + +_EVENT_RUNTIME_KEYS = frozenset( + { + "provider", + "instrument", + "instrument_type", + "timeframe", + "bid", + "ask", + "last_price", + "public_timestamp_utc", + "decision_at_utc", + "instrument_status", + "settlement_asset", + "tick_size", + "lot_size", + "minimum_quantity", + "maximum_quantity", + "direction", + "quantity", + "limit_price", + "order_type", + "quality_flags", + } +) +_QUALITY_FLAGS = frozenset( + { + "deterministic_source_gap", + "missing_observation", + "out_of_order_timestamp", + "duplicate_timestamp", + "stale_timestamp", + "instrument_metadata_churn", + "tick_size_boundary", + "lot_size_boundary", + "instrument_status_boundary", + } +) +_FORBIDDEN_DEPENDENCY_SYMBOLS = ( + "transport", + "http_client", + "authenticated_venue", + "production_broker", + "credential_resolver", + "operator_database_connector", + "authenticated_proof_runner", + "operator_bootstrap_executor", + "authenticated_endpoint_transport", + "arbitrary_endpoint_transport", + "operator_database_connection", + "fix_transport", + "production_fix_transport", + "leverage", + "derivatives", + "production_fix", + "submit_order", + "cancel_order", + "withdraw", + "transfer", + "borrow", + "set_leverage", + "send", + "request_endpoint", + "resolve_credentials", + "connect_operator_database", +) + + +def _digest(value: str, name: str) -> str: + if ( + type(value) is not str + or len(value) != 64 + or any(character not in "0123456789abcdef" for character in value) + ): + raise ValueError(f"{name} must be a lowercase SHA-256 digest") + return value + + +def _nonnegative(value: int, name: str) -> int: + if type(value) is not int or value < 0: + raise ValueError(f"{name} must be a non-negative integer") + return value + + +def _pairs(values, name: str) -> tuple[tuple[str, str], ...]: + result = tuple( + (str(key), _digest(value, f"{name} value")) for key, value in values + ) + if len({key for key, _ in result}) != len(result): + raise ValueError(f"{name} keys must be unique") + return result + + +def _counts(values: Mapping[str, int]) -> Mapping[str, int]: + result = { + str(key): _nonnegative(value, f"count[{key}]") + for key, value in values.items() + } + return MappingProxyType(dict(sorted(result.items()))) + + +def _segment_decision_counts( + values: Mapping[str, Mapping[str, int]], +) -> Mapping[str, Mapping[str, int]]: + """Freeze the exact public per-segment accepted/blocked metric shape.""" + + result: dict[str, Mapping[str, int]] = {} + for segment_id, counts in values.items(): + if type(segment_id) is not str or not segment_id: + raise ValueError("segment decision count keys must be non-empty strings") + if not isinstance(counts, Mapping) or frozenset(counts) != { + "accepted", + "blocked", + }: + raise ValueError( + "segment decision counts require exact accepted and blocked keys" + ) + result[segment_id] = MappingProxyType( + { + "accepted": _nonnegative(counts["accepted"], "segment accepted"), + "blocked": _nonnegative(counts["blocked"], "segment blocked"), + } + ) + return MappingProxyType(dict(sorted(result.items()))) + + +def derive_shadow_campaign_run_id( + *, + campaign_id: str, + event_index: int, + event_sha256: str, + synthetic_account_snapshot_sha256: str, + repository_sha: str, + campaign_spec_sha256: str, +) -> UUID: + """Derive event authority without wall-clock, process, or machine inputs.""" + + if type(repository_sha) is not str: + raise TypeError("repository_sha must be exact text") + if type(campaign_id) is not str or not campaign_id: + raise ValueError("campaign_id must be non-empty") + _nonnegative(event_index, "event_index") + return shadow_uuid( + "historical-campaign-event-run", + { + "campaign_id": campaign_id, + "event_index": event_index, + "event_sha256": _digest(event_sha256, "event_sha256"), + "synthetic_account_snapshot_sha256": _digest( + synthetic_account_snapshot_sha256, + "synthetic_account_snapshot_sha256", + ), + "repository_sha": validate_git_commit_sha(repository_sha), + "campaign_spec_sha256": _digest( + campaign_spec_sha256, "campaign_spec_sha256" + ), + }, + ) +_POLICY_INTENT_SEMANTIC_FIELDS = ( + "instrument", + "side", + "order_type", + "quantity", + "limit_price", + "expected_notional", + "risk_accepted", + "blockers", + "approval_result", + "would_submit_classification", + "shadow_only", + "production_write_enabled", + "submit_reachable", + "cancel_reachable", + "transport_called", +) + + +def _validated_policy_bundle(bundle: Mapping[str, object]) -> dict[str, object]: + if not isinstance(bundle, Mapping): + raise TypeError("campaign policy bundle must be a mapping") + return validate_shadow_bundle_payload(bundle) + + +def campaign_policy_input_sha256( + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, +) -> str: + """Hash only the exact point-in-time event and synthetic-account inputs.""" + + _validate_shadow_campaign_event_authority(event) + _validate_synthetic_account_snapshot_authority(account) + event_payload = event.runtime_payload + if frozenset(event_payload) != _EVENT_RUNTIME_KEYS: + raise ValueError("campaign policy event runtime payload is not canonical") + return sha256_payload( + { + "operation": "phase8b-shadow-campaign-policy-input-v1", + "event_runtime_payload": event_payload, + "account_runtime_payload": account.runtime_payload, + } + ) + + +def _policy_decision_sha256( + policy_input_sha256: str, + bundle: Mapping[str, object], +) -> str: + decision = bundle["decision"] + intent = decision["shadow_intent"] + semantic_intent = ( + None + if intent is None + else { + field_name: ( + tuple(intent[field_name]) + if field_name == "blockers" + else intent[field_name] + ) + for field_name in _POLICY_INTENT_SEMANTIC_FIELDS + } + ) + return sha256_payload( + { + "operation": "phase8b-shadow-campaign-policy-decision-v1", + "policy_input_sha256": _digest( + policy_input_sha256, "policy_input_sha256" + ), + "accepted": decision["accepted"], + "blockers": tuple(decision["blockers"]), + "shadow_intent": semantic_intent, + } + ) + + +def campaign_policy_decision_sha256( + policy_input_sha256: str, + bundle: Mapping[str, object], +) -> str: + """Project a canonical bundle onto lineage-free policy decision semantics.""" + + return _policy_decision_sha256( + policy_input_sha256, + _validated_policy_bundle(bundle), + ) + + +def _policy_summary_sha256( + policy_input_sha256: str, + policy_decision_sha256: str, + bundle: Mapping[str, object], +) -> str: + summary = bundle["summary"] + return sha256_payload( + { + "operation": "phase8b-shadow-campaign-policy-summary-v1", + "policy_input_sha256": _digest( + policy_input_sha256, "policy_input_sha256" + ), + "policy_decision_sha256": _digest( + policy_decision_sha256, "policy_decision_sha256" + ), + "accepted": summary["accepted"], + "blockers": tuple(summary["blockers"]), + "shadow_intent_count": summary["shadow_intent_count"], + } + ) + + +def campaign_policy_summary_sha256( + policy_input_sha256: str, + policy_decision_sha256: str, + bundle: Mapping[str, object], +) -> str: + """Project a canonical bundle onto lineage-free policy summary semantics.""" + + validated = _validated_policy_bundle(bundle) + expected_decision_sha256 = _policy_decision_sha256( + policy_input_sha256, validated + ) + if policy_decision_sha256 != expected_decision_sha256: + raise ValueError( + "policy_decision_sha256 does not match the canonical policy decision" + ) + return _policy_summary_sha256( + policy_input_sha256, + policy_decision_sha256, + validated, + ) + + +def _campaign_policy_components( + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, + bundle: Mapping[str, object], +) -> tuple[dict[str, object], Mapping[str, object]]: + validated = _validated_policy_bundle(bundle) + if validated["decision"]["scenario_id"] != _scenario_id(event, account): + raise ValueError("campaign policy bundle does not match the event context") + input_sha256 = campaign_policy_input_sha256(event, account) + decision_sha256 = _policy_decision_sha256(input_sha256, validated) + summary_sha256 = _policy_summary_sha256( + input_sha256, + decision_sha256, + validated, + ) + decision = validated["decision"] + summary = validated["summary"] + projection = MappingProxyType( + { + "policy_input_sha256": input_sha256, + "policy_decision_sha256": decision_sha256, + "policy_summary_sha256": summary_sha256, + "accepted": decision["accepted"], + "blockers": tuple(decision["blockers"]), + "shadow_intent_count": summary["shadow_intent_count"], + } + ) + return validated, projection + + +def campaign_policy_hash_projection( + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, + bundle: Mapping[str, object], +) -> Mapping[str, object]: + """Return canonical lineage-free per-event policy hashes and outcome facts.""" + + return _campaign_policy_components(event, account, bundle)[1] + + +def campaign_policy_chain_seed() -> str: + """Return the lineage-free seed for a full-prefix campaign policy chain.""" + + return sha256_payload({"operation": SHADOW_CAMPAIGN_POLICY_CHAIN_VERSION}) + + +def _segment_policy_chain_seed() -> str: + return sha256_payload( + {"operation": "phase8b-historical-shadow-local-segment-policy-chain-v1"} + ) + + +def _campaign_policy_chain_step( + *, + prior_chain_sha256: str, + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, + bundle: Mapping[str, object], + projection: Mapping[str, object], +) -> str: + decision = bundle["decision"] + return sha256_payload( + { + "prior_chain_sha256": _digest( + prior_chain_sha256, "prior_chain_sha256" + ), + "event_sha256": event.event_sha256, + "account_runtime_input_sha256": account.runtime_payload_sha256, + "synthetic_account_snapshot_sha256": account.snapshot_sha256, + "policy_input_sha256": projection["policy_input_sha256"], + "policy_decision_sha256": projection["policy_decision_sha256"], + "policy_summary_sha256": projection["policy_summary_sha256"], + "blockers": projection["blockers"], + "durable_provenance_sha256": decision["data_provenance_hash"], + "safety_facts_sha256": decision["safety_facts_hash"], + } + ) + +def _campaign_authority_chain_seed() -> str: + return sha256_payload({"operation": SHADOW_CAMPAIGN_CHAIN_VERSION}) + + +def _segment_authority_chain_seed() -> str: + return sha256_payload( + {"operation": "phase8b-historical-shadow-local-segment-authority-chain-v1"} + ) + + +def _campaign_authority_chain_step( + *, + prior_chain_sha256: str, + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, + bundle: Mapping[str, object], +) -> str: + """Bind the exact canonical shadow authorities required by the campaign.""" + + validated = _validated_policy_bundle(bundle) + if validated["decision"]["scenario_id"] != _scenario_id(event, account): + raise ValueError("campaign authority bundle does not match the event context") + decision = validated["decision"] + summary = validated["summary"] + return sha256_payload( + { + "prior_chain_sha256": _digest( + prior_chain_sha256, "prior_chain_sha256" + ), + "event_sha256": event.event_sha256, + "synthetic_account_snapshot_sha256": account.snapshot_sha256, + "shadow_decision_sha256": decision["decision_hash"], + "shadow_summary_sha256": summary["summary_hash"], + "blockers": tuple(decision["blockers"]), + "durable_provenance_sha256": decision["data_provenance_hash"], + "safety_facts_sha256": decision["safety_facts_hash"], + } + ) + +def campaign_policy_chain_step( + *, + prior_chain_sha256: str, + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, + projection: Mapping[str, object], + bundle: Mapping[str, object], +) -> str: + """Advance one link after exact-checking a caller-supplied projection.""" + + if not isinstance(projection, Mapping): + raise TypeError("campaign policy projection must be a mapping") + validated, expected_projection = _campaign_policy_components( + event, + account, + bundle, + ) + if dict(projection) != dict(expected_projection): + raise ValueError("campaign policy projection is not canonical") + return _campaign_policy_chain_step( + prior_chain_sha256=prior_chain_sha256, + event=event, + account=account, + bundle=validated, + projection=expected_projection, + ) + + +def campaign_policy_chain_sha256( + *, + prior_chain_sha256: str, + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, + bundle: Mapping[str, object], +) -> str: + """Advance a canonical policy chain by one validated event bundle.""" + + validated, projection = _campaign_policy_components(event, account, bundle) + return _campaign_policy_chain_step( + prior_chain_sha256=prior_chain_sha256, + event=event, + account=account, + bundle=validated, + projection=projection, + ) + + +@dataclass(frozen=True, slots=True) +class ShadowCampaignProgress: + campaign_id: str + campaign_spec_sha256: str + repository_sha: str + total_event_count: int + scanned_event_count: int + completed_event_count: int + completed_prefix_count: int + missing_event_count: int + valid_bundle_count: int + invalid_bundle_count: int + first_missing_index: int | None + accepted_decision_count: int + blocked_decision_count: int + shadow_intent_count: int + blocker_frequencies: Mapping[str, int] + segment_completion: Mapping[str, int] + segment_decision_counts: Mapping[str, Mapping[str, int]] + segment_result_hashes: tuple[tuple[str, str], ...] + window_result_hashes: tuple[tuple[str, str], ...] + event_sequence_sha256: str + decision_chain_sha256: str + campaign_result_sha256: str | None + safety_facts: ShadowSafetyFacts + complete: bool + progress_sha256: str = field(init=False) + + def __post_init__(self) -> None: + if type(self.repository_sha) is not str: + raise TypeError("campaign progress repository SHA must be exact text") + validate_git_commit_sha(self.repository_sha) + for name in ( + "total_event_count", + "scanned_event_count", + "completed_event_count", + "completed_prefix_count", + "missing_event_count", + "valid_bundle_count", + "invalid_bundle_count", + "accepted_decision_count", + "blocked_decision_count", + "shadow_intent_count", + ): + _nonnegative(getattr(self, name), name) + if self.first_missing_index is not None: + _nonnegative(self.first_missing_index, "first_missing_index") + object.__setattr__( + self, "blocker_frequencies", _counts(self.blocker_frequencies) + ) + object.__setattr__( + self, "segment_completion", _counts(self.segment_completion) + ) + object.__setattr__( + self, + "segment_decision_counts", + _segment_decision_counts(self.segment_decision_counts), + ) + object.__setattr__( + self, + "segment_result_hashes", + _pairs(self.segment_result_hashes, "segment_result_hashes"), + ) + object.__setattr__( + self, + "window_result_hashes", + _pairs(self.window_result_hashes, "window_result_hashes"), + ) + _digest(self.event_sequence_sha256, "event_sequence_sha256") + _digest(self.decision_chain_sha256, "decision_chain_sha256") + if self.campaign_result_sha256 is not None: + _digest(self.campaign_result_sha256, "campaign_result_sha256") + if set(self.segment_decision_counts) != set(self.segment_completion): + raise ValueError( + "segment decision counts must cover the segment completion map" + ) + if sum( + counts["accepted"] for counts in self.segment_decision_counts.values() + ) != self.accepted_decision_count or sum( + counts["blocked"] for counts in self.segment_decision_counts.values() + ) != self.blocked_decision_count: + raise ValueError("segment decision counts disagree with campaign totals") + if ( + self.accepted_decision_count + self.blocked_decision_count + != self.completed_event_count + ): + raise ValueError("campaign decision totals must cover completed events") + if self.complete != (self.missing_event_count == 0): + raise ValueError( + "campaign progress completeness disagrees with missing rows" + ) + if self.complete and self.campaign_result_sha256 is None: + raise ValueError("complete campaign progress requires a result hash") + core = { + name: getattr(self, name) + for name in self.__dataclass_fields__ + if name != "progress_sha256" + } + core["blocker_frequencies"] = dict(self.blocker_frequencies) + core["segment_completion"] = dict(self.segment_completion) + core["segment_decision_counts"] = { + segment_id: dict(counts) + for segment_id, counts in self.segment_decision_counts.items() + } + core["safety_facts"] = { + name: getattr(self.safety_facts, name) + for name in self.safety_facts.__dataclass_fields__ + } + + object.__setattr__(self, "progress_sha256", sha256_payload(core)) + + @property + def segment_hashes(self) -> Mapping[str, str]: + return MappingProxyType(dict(self.segment_result_hashes)) + + @property + def window_hashes(self) -> Mapping[str, str]: + return MappingProxyType(dict(self.window_result_hashes)) + + +@dataclass(frozen=True, slots=True) +class ShadowCampaignResult: + campaign_id: str + campaign_spec_sha256: str + repository_sha: str + total_event_count: int + processed_event_count: int + committed_event_count: int + completed_event_count: int + first_missing_index: int | None + accepted_decision_count: int + blocked_decision_count: int + shadow_intent_count: int + blocker_frequencies: Mapping[str, int] + segment_decision_counts: Mapping[str, Mapping[str, int]] + persisted_count: int + replay_count: int + segment_result_hashes: tuple[tuple[str, str], ...] + window_result_hashes: tuple[tuple[str, str], ...] + event_sequence_sha256: str + final_decision_chain_sha256: str + campaign_result_sha256: str | None + safety_facts: ShadowSafetyFacts + complete: bool + + def __post_init__(self) -> None: + if type(self.repository_sha) is not str: + raise TypeError("campaign result repository SHA must be exact text") + validate_git_commit_sha(self.repository_sha) + for name in ( + "total_event_count", + "processed_event_count", + "committed_event_count", + "completed_event_count", + "accepted_decision_count", + "blocked_decision_count", + "shadow_intent_count", + "persisted_count", + "replay_count", + ): + _nonnegative(getattr(self, name), name) + if self.first_missing_index is not None: + _nonnegative(self.first_missing_index, "first_missing_index") + object.__setattr__( + self, "blocker_frequencies", _counts(self.blocker_frequencies) + ) + object.__setattr__( + self, + "segment_decision_counts", + _segment_decision_counts(self.segment_decision_counts), + ) + object.__setattr__( + self, + "segment_result_hashes", + _pairs(self.segment_result_hashes, "segment_result_hashes"), + ) + object.__setattr__( + self, + "window_result_hashes", + _pairs(self.window_result_hashes, "window_result_hashes"), + ) + _digest(self.event_sequence_sha256, "event_sequence_sha256") + _digest(self.final_decision_chain_sha256, "final_decision_chain_sha256") + if self.campaign_result_sha256 is not None: + _digest(self.campaign_result_sha256, "campaign_result_sha256") + if sum( + counts["accepted"] for counts in self.segment_decision_counts.values() + ) != self.accepted_decision_count or sum( + counts["blocked"] for counts in self.segment_decision_counts.values() + ) != self.blocked_decision_count: + raise ValueError("segment decision counts disagree with campaign totals") + if ( + self.accepted_decision_count + self.blocked_decision_count + != self.completed_event_count + ): + raise ValueError("campaign decision totals must cover completed events") + if self.complete != (self.first_missing_index is None): + raise ValueError( + "campaign result completeness disagrees with first missing event" + ) + if self.complete and self.campaign_result_sha256 is None: + raise ValueError("complete campaign result requires a final hash") + + @property + def segment_hashes(self) -> Mapping[str, str]: + return MappingProxyType(dict(self.segment_result_hashes)) + + @property + def window_hashes(self) -> Mapping[str, str]: + return MappingProxyType(dict(self.window_result_hashes)) + + +ShadowCampaignRunResult = ShadowCampaignResult + + +_FAILURE_STAGES = ( + "repository_validation", + "event_execution", + "final_repository_validation", +) + + +class _FrozenFailureProgress(Mapping[str, object]): + """Immutable exact-dict carrier with callback-free provenance checks.""" + + __slots__ = ("_values",) + + def __init__(self, values: Mapping[str, object]) -> None: + if type(values) is not dict: + raise TypeError("campaign failure progress must be an exact mapping") + if any(type(key) is not str for key in values): + raise TypeError("campaign failure progress keys must be exact text") + object.__setattr__(self, "_values", dict(values)) + + def __getitem__(self, key: str) -> object: + return object.__getattribute__(self, "_values")[key] + + def __iter__(self): + return iter(object.__getattribute__(self, "_values")) + + def __len__(self) -> int: + return len(object.__getattribute__(self, "_values")) + + +def _freeze_failure_progress(progress: Mapping[str, object]) -> Mapping[str, object]: + return _FrozenFailureProgress(progress) + + +def _copy_shadow_campaign_failure_progress( + progress: Mapping[str, object], +) -> dict[str, object]: + """Copy only the module's exact, untampered immutable progress carrier.""" + + if type(progress) is not _FrozenFailureProgress: + raise TypeError("campaign failure progress carrier is not canonical") + values = object.__getattribute__(progress, "_values") + if type(values) is not dict: + raise TypeError("campaign failure progress carrier was mutated") + if any(type(key) is not str for key in values): + raise TypeError("campaign failure progress keys must be exact text") + return dict(values) + + +class ShadowCampaignSpecConflict(ValueError): + """A campaign ID is already bound to a different persisted specification.""" + + def __init__( + self, + message: str, + *, + progress: Mapping[str, object] | None = None, + failure_stage: str | None = None, + ) -> None: + if type(message) is not str: + raise TypeError("campaign conflict message must be exact text") + if (progress is None) != (failure_stage is None): + raise ValueError("campaign conflict progress and stage must be paired") + if failure_stage is not None and ( + type(failure_stage) is not str or failure_stage not in _FAILURE_STAGES + ): + raise ValueError("campaign conflict failure stage is not reviewed") + self.progress = ( + None if progress is None else _freeze_failure_progress(progress) + ) + self.failure_stage = failure_stage + super().__init__(message) + + +class ShadowCampaignInjectedCrash(RuntimeError): + """Public-safe injected failure with truthful operation progress.""" + + def __init__(self, crash_point: str, progress: Mapping[str, object]) -> None: + if type(crash_point) is not str: + raise TypeError("campaign crash point must be exact text") + self.crash_point = crash_point + self.progress = _freeze_failure_progress(progress) + super().__init__( + f"injected historical shadow campaign crash at {crash_point}" + ) + + +class ShadowCampaignOperationFailure(RuntimeError): + """Public-safe campaign failure carrying repository-derived progress.""" + + def __init__( + self, + progress: Mapping[str, object], + *, + failure_stage: str, + ) -> None: + if type(failure_stage) is not str or failure_stage not in _FAILURE_STAGES: + raise ValueError("campaign operation failure stage is not reviewed") + self.progress = _freeze_failure_progress(progress) + self.failure_stage = failure_stage + super().__init__("historical shadow campaign operation failed closed") + + +@dataclass(frozen=True, slots=True) +class _EventContext: + event: ShadowCampaignEvent + account: SyntheticAccountTimelineSnapshot + shadow_run_id: UUID + scenario_id: str + + +@dataclass(slots=True) +class _Accumulator: + spec: ShadowCampaignSpec + repository_sha: str + event_sequence_sha256: str + decision_chain_sha256: str + event_count: int = 0 + accepted_count: int = 0 + blocked_count: int = 0 + intent_count: int = 0 + blocker_frequencies: dict[str, int] = field(default_factory=dict) + segment_decision_counts: dict[str, dict[str, int]] = field( + default_factory=dict + ) + segment_result_hashes: list[tuple[str, str]] = field(default_factory=list) + window_result_hashes: list[tuple[str, str]] = field(default_factory=list) + current_segment_id: str | None = None + current_segment_first_policy_chain_sha256: str | None = None + current_segment_policy_chain_sha256: str | None = None + current_segment_event_count: int = 0 + + @classmethod + def create( + cls, spec: ShadowCampaignSpec, repository_sha: str + ) -> "_Accumulator": + return cls( + spec=spec, + repository_sha=repository_sha, + event_sequence_sha256=sha256_payload( + { + "operation": "phase8b-shadow-campaign-data-event-sequence-v2", + } + ), + decision_chain_sha256=_campaign_authority_chain_seed(), + segment_decision_counts={ + segment.segment_id: {"accepted": 0, "blocked": 0} + for segment in spec.segments + }, + ) + + def record_bundle( + self, + context: _EventContext, + bundle: Mapping[str, object], + ) -> None: + event = context.event + if event.event_index != self.event_count: + raise ValueError("campaign decision chain is not a contiguous prefix") + validated_bundle, projection = _campaign_policy_components( + event, + context.account, + bundle, + ) + next_event_sequence_sha256 = sha256_payload( + { + "prior_sha256": self.event_sequence_sha256, + "event_sha256": event.event_sha256, + } + ) + if self.current_segment_id is None: + if event.segment_event_index != 0: + raise ValueError("campaign segment does not begin at its first event") + segment_chain_prior = _segment_authority_chain_seed() + elif self.current_segment_id != event.segment_id: + raise ValueError("prior campaign segment was not finalized") + elif self.current_segment_policy_chain_sha256 is None: + raise ValueError("campaign segment policy chain is unavailable") + else: + segment_chain_prior = self.current_segment_policy_chain_sha256 + + next_decision_chain_sha256 = _campaign_authority_chain_step( + prior_chain_sha256=self.decision_chain_sha256, + event=event, + account=context.account, + bundle=validated_bundle, + ) + next_segment_chain_sha256 = _campaign_authority_chain_step( + prior_chain_sha256=segment_chain_prior, + event=event, + account=context.account, + bundle=validated_bundle, + ) + + self.event_sequence_sha256 = next_event_sequence_sha256 + self.decision_chain_sha256 = next_decision_chain_sha256 + if self.current_segment_id is None: + self.current_segment_id = event.segment_id + self.current_segment_event_count = 0 + self.current_segment_first_policy_chain_sha256 = next_segment_chain_sha256 + self.current_segment_policy_chain_sha256 = next_segment_chain_sha256 + self.current_segment_event_count += 1 + self.event_count += 1 + + blockers = tuple(projection["blockers"]) + accepted = bool(projection["accepted"]) + self.accepted_count += int(accepted) + self.blocked_count += int(not accepted) + decision_kind = "accepted" if accepted else "blocked" + self.segment_decision_counts[event.segment_id][decision_kind] += 1 + self.intent_count += int(projection["shadow_intent_count"]) + for blocker in blockers: + self.blocker_frequencies[blocker] = ( + self.blocker_frequencies.get(blocker, 0) + 1 + ) + windows = dict(self.spec.windows) + for label, count in windows.items(): + if self.event_count == count: + self.window_result_hashes.append( + ( + label, + sha256_payload( + { + "window_label": label, + "event_count": count, + "final_decision_chain_sha256": self.decision_chain_sha256, + } + ), + ) + ) + + def complete_segment(self, event: ShadowCampaignEvent) -> None: + segment = self.spec.segments[event.segment_index] + if ( + event.event_index + 1 != segment.end_event_index_exclusive + or self.current_segment_id != segment.segment_id + or self.current_segment_first_policy_chain_sha256 is None + or self.current_segment_policy_chain_sha256 is None + or self.current_segment_event_count != segment.event_count + ): + raise ValueError("campaign segment completion is not canonical") + self.segment_result_hashes.append( + ( + segment.segment_id, + sha256_payload( + { + "operation": ( + "phase8b-historical-shadow-segment-authority-result-v1" + ), + "segment_sha256": segment.segment_sha256, + "event_count": self.current_segment_event_count, + "first_segment_chain_sha256": ( + self.current_segment_first_policy_chain_sha256 + ), + "final_segment_chain_sha256": ( + self.current_segment_policy_chain_sha256 + ), + } + ), + ) + ) + self.current_segment_id = None + self.current_segment_first_policy_chain_sha256 = None + self.current_segment_policy_chain_sha256 = None + self.current_segment_event_count = 0 + + def campaign_result_sha256(self) -> str: + if self.event_count != self.spec.event_count: + raise ValueError("incomplete campaign has no final result hash") + if len(self.segment_result_hashes) != self.spec.segment_count: + raise ValueError("campaign segment hashes are incomplete") + if len(self.window_result_hashes) != len(self.spec.window_labels): + raise ValueError("campaign window hashes are incomplete") + if sum( + counts["accepted"] for counts in self.segment_decision_counts.values() + ) != self.accepted_count or sum( + counts["blocked"] for counts in self.segment_decision_counts.values() + ) != self.blocked_count: + raise ValueError("segment decision counts disagree with campaign totals") + if self.accepted_count + self.blocked_count != self.event_count: + raise ValueError( + "campaign decision counts must cover the complete event sequence" + ) + return sha256_payload( + { + "operation": "phase8b-historical-shadow-campaign-result-v1", + "campaign_id": self.spec.campaign_id, + "campaign_spec_sha256": self.spec.spec_sha256, + "repository_sha": self.repository_sha, + "event_count": self.event_count, + "event_sequence_sha256": self.event_sequence_sha256, + "accepted_decision_count": self.accepted_count, + "blocked_decision_count": self.blocked_count, + "shadow_intent_count": self.intent_count, + "segment_decision_counts": { + segment_id: dict(counts) + for segment_id, counts in sorted(self.segment_decision_counts.items()) + }, + "blocker_frequencies": dict(sorted(self.blocker_frequencies.items())), + "segment_result_hashes": tuple(self.segment_result_hashes), + "window_result_hashes": tuple(self.window_result_hashes), + "final_decision_chain_sha256": self.decision_chain_sha256, + "safety_facts_sha256": ShadowSafetyFacts(0).record_hash, + } + ) + + +def _scenario_id( + event: ShadowCampaignEvent, account: SyntheticAccountTimelineSnapshot +) -> str: + return ( + f"{shadow_campaign_scenario_prefix(event.campaign_id)}" + f"{event.campaign_spec_sha256}:event:" + f"{event.event_id}:{event.event_sha256}:account:{account.snapshot_sha256}" + ) + + +def campaign_scenario_for_event( + event: ShadowCampaignEvent, + account: SyntheticAccountTimelineSnapshot, +) -> GeneratedShadowScenarioSpec: + """Adapt exact generated contracts to the accepted fixture runtime contract.""" + + _validate_shadow_campaign_event_authority(event) + _validate_synthetic_account_snapshot_authority(account) + payload = dict(event.runtime_payload) + if frozenset(payload) != _EVENT_RUNTIME_KEYS: + raise ValueError("campaign event runtime payload fields are not canonical") + flags = tuple(payload.pop("quality_flags")) + if any(type(value) is not str for value in flags) or not set(flags) <= _QUALITY_FLAGS: + raise ValueError("campaign event has unsupported quality flags") + unavailable = bool( + {"deterministic_source_gap", "missing_observation"}.intersection(flags) + ) + duplicate = "duplicate_timestamp" in flags + market_payload = { + "provider": payload["provider"], + "instrument": payload["instrument"], + "instrument_type": payload["instrument_type"], + "bid": payload["bid"], + "ask": payload["ask"], + "last_price": payload["last_price"], + "public_timestamp_utc": payload["public_timestamp_utc"], + "instrument_status": payload["instrument_status"], + "settlement_asset": payload["settlement_asset"], + "tick_size": payload["tick_size"], + "lot_size": payload["lot_size"], + "minimum_quantity": payload["minimum_quantity"], + "maximum_quantity": payload["maximum_quantity"], + "source_identity": "phase8b-historical-shadow-corpus-fixture-v1", + "classification": "fixture", + "network_read_count": 0, + "response_rows": 2 if duplicate else 1, + "metadata_present": True, + "response_complete": not unavailable, + "provider_code": "0", + "replayed": False, + "cached": False, + "conflicting_sources": False, + "fixture_declared_operational": False, + "operational_declared_fixture": False, + "failure_kind": "partial_page" if unavailable else None, + } + request_payload = { + "direction": payload["direction"], + "quantity": payload["quantity"], + "limit_price": payload["limit_price"], + "order_type": payload["order_type"], + "instrument": payload["instrument"], + "decision_at_utc": payload["decision_at_utc"], + "timeframe": payload["timeframe"], + "campaign_event_hash": event.event_sha256, + } + return GeneratedShadowScenarioSpec( + _scenario_id(event, account), + "market", + scenario_account_payload(account), + market_payload, + request_payload, + ) + + +class ShadowCampaignRuntime: + """Execute, inspect, replay, and resume canonical per-event shadow bundles.""" + + def __init__( + self, + repository: MemoryShadowRepository | PostgresShadowRepository, + repository_sha: str, + *, + spec: ShadowCampaignSpec = FULL_90D_CAMPAIGN_SPEC, + account_timeline: tuple[SyntheticAccountTimelineSnapshot, ...] = ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ), + ) -> None: + if type(repository_sha) is not str: + raise TypeError("campaign repository SHA must be exact text") + if type(repository) not in (MemoryShadowRepository, PostgresShadowRepository): + raise ShadowAuthorityError("unsupported shadow campaign repository") + if callable(repository): + raise ShadowAuthorityError("shadow campaign repository cannot be callable") + for name in _FORBIDDEN_DEPENDENCY_SYMBOLS: + if hasattr(repository, name): + raise ShadowAuthorityError( + "shadow campaign repository exposes forbidden authority" + ) + if type(repository) is PostgresShadowRepository: + validate_shadow_campaign_database_name(repository.expected_database) + try: + _validate_campaign_spec_authority(spec) + except (TypeError, ValueError, PermissionError) as exc: + raise ShadowAuthorityError( + "campaign spec exposes non-canonical authority" + ) from exc + if type(account_timeline) is not tuple or not account_timeline or any( + type(item) is not SyntheticAccountTimelineSnapshot + for item in account_timeline + ): + raise TypeError( + "campaign timeline must be an exact tuple of synthetic snapshots" + ) + expected_start = 0 + for snapshot in account_timeline: + _validate_synthetic_account_snapshot_authority(snapshot) + if snapshot.effective_start_event_index != expected_start: + raise ValueError("campaign account timeline has a gap or overlap") + expected_start = snapshot.effective_end_event_index + if expected_start < spec.event_count: + raise ValueError("campaign account timeline does not cover the specification") + self.repository = repository + self.repository_sha = validate_git_commit_sha(repository_sha) + self.spec = spec + self.account_timeline = account_timeline + self.account_timeline_sha256 = synthetic_account_timeline_sha256( + account_timeline + ) + if self.spec.campaign_id == FULL_90D_CAMPAIGN_SPEC.campaign_id: + canonical_timeline_sha256 = synthetic_account_timeline_sha256( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ) + if ( + self.account_timeline != FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + or self.account_timeline_sha256 != canonical_timeline_sha256 + ): + raise ValueError( + "canonical full campaign ID requires the canonical " + "synthetic account timeline" + ) + self._identity = RuntimeRepositoryIdentity( + self.repository_sha, "git_checkout" + ) + self._runtime = ShadowAssuranceRuntime( + repository=repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: self._identity, + ) + + def _validate_spec(self) -> None: + if ( + type(self.spec) is not ShadowCampaignSpec + or self.spec.timeframe != "5m" + or self.spec.network_access_allowed + or self.spec.credential_access_allowed + or self.spec.user_file_access_allowed + or not self.spec.repository_owned + ): + raise PermissionError("historical shadow campaign spec is not safe") + if self.spec.campaign_id == FULL_90D_CAMPAIGN_SPEC.campaign_id and ( + self.spec != FULL_90D_CAMPAIGN_SPEC + or self.spec.spec_sha256 != FULL_90D_CAMPAIGN_SPEC.spec_sha256 + ): + raise ValueError( + "canonical full campaign ID conflicts with a different specification" + ) + observed_spec_hashes = self.repository.observed_campaign_spec_hashes( + self.spec.campaign_id + ) + if observed_spec_hashes - {self.spec.spec_sha256}: + # Persist also enforces this binding atomically, closing simultaneous + # first-start races without adding an aggregate campaign row. + raise ShadowCampaignSpecConflict( + "campaign ID is already bound to a different persisted specification" + ) + observed_count = 0 + maximum_observed_index = -1 + for event_id, _, _ in ( + self.repository.iter_campaign_event_authorities( + self.spec.campaign_id, self.spec.spec_sha256 + ) + ): + event_index = shadow_campaign_event_index_from_id(event_id) + observed_count += 1 + maximum_observed_index = max(maximum_observed_index, event_index) + if event_index >= self.spec.event_count: + raise ShadowCampaignSpecConflict( + "campaign contains an event outside the specification" + ) + matched_count = 0 + for context in self._contexts(0, maximum_observed_index + 1): + authority = self.repository.campaign_event_authority( + self.spec.campaign_id, + self.spec.spec_sha256, + context.event.event_id, + ) + if authority is None: + continue + matched_count += 1 + run_id, scenario_id = authority + if ( + str(context.shadow_run_id) != run_id + or context.scenario_id != scenario_id + ): + raise ShadowCampaignSpecConflict( + "campaign logical event has a different persisted authority" + ) + if matched_count != observed_count: + raise ShadowCampaignSpecConflict( + "campaign logical-event authority set is not canonical" + ) + + def _validate_stop(self, stop_index: int | None) -> int: + if stop_index is None: + return self.spec.event_count + if ( + type(stop_index) is not int + or not 0 <= stop_index <= self.spec.event_count + ): + raise ValueError("campaign stop_index is outside the specification") + return stop_index + + def _contexts(self, start_index: int, stop_index: int): + for expected_index, event in enumerate( + iter_shadow_campaign_events( + self.spec, start_index=start_index, stop_index=stop_index + ), + start=start_index, + ): + if type(event) is not ShadowCampaignEvent: + raise TypeError("campaign generator yielded an unsupported event type") + if ( + event.event_index != expected_index + or event.campaign_id != self.spec.campaign_id + or event.campaign_spec_sha256 != self.spec.spec_sha256 + or event.timeframe != self.spec.timeframe + ): + raise ValueError("campaign generator event identity mismatch") + account = synthetic_account_snapshot_for_event( + event.event_index, self.account_timeline + ) + if type(account) is not SyntheticAccountTimelineSnapshot: + raise TypeError("campaign timeline returned an unsupported snapshot") + run_id = derive_shadow_campaign_run_id( + campaign_id=self.spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=self.repository_sha, + campaign_spec_sha256=self.spec.spec_sha256, + ) + yield _EventContext( + event, account, run_id, _scenario_id(event, account) + ) + + def _loaded_contexts(self): + batch: list[_EventContext] = [] + + def load(values: tuple[_EventContext, ...]): + run_ids = tuple(value.shadow_run_id for value in values) + loader = getattr(self.repository, "iter_expected_bundles", None) + if loader is None: + bundles = tuple( + self.repository.load_bundle(run_id) for run_id in run_ids + ) + else: + bundles = tuple(loader(run_ids, batch_size=len(run_ids))) + if len(bundles) != len(values): + raise ValueError("campaign repository returned a truncated read batch") + return zip(values, bundles) + + for context in self._contexts(0, self.spec.event_count): + batch.append(context) + if len(batch) == 256: + yield from load(tuple(batch)) + batch.clear() + if batch: + yield from load(tuple(batch)) + + def _validate_bundle( + self, context: _EventContext, payload: object + ) -> dict[str, object]: + bundle = validate_shadow_bundle_payload(payload) + decision = bundle["decision"] + provenance = decision["data_provenance"] + if ( + decision["shadow_run_id"] != str(context.shadow_run_id) + or decision["scenario_id"] != context.scenario_id + or decision["repository_commit_sha"] != self.repository_sha + or provenance["classification"] != "fixture" + or provenance["network_read_count"] != 0 + ): + raise ValueError("canonical shadow bundle has wrong campaign authority") + return bundle + + @staticmethod + def _is_segment_end(spec: ShadowCampaignSpec, event: ShadowCampaignEvent) -> bool: + return ( + event.event_index + 1 + == spec.segments[event.segment_index].end_event_index_exclusive + ) + + def _campaign_failure_progress( + self, + *, + processed_count: int, + committed_count: int, + replay_count: int, + first_missing_index: int | None, + campaign_result_sha256: str | None = None, + ) -> dict[str, object]: + facts = ShadowSafetyFacts(0) + return { + "campaign_id": self.spec.campaign_id, + "campaign_spec_sha256": self.spec.spec_sha256, + "processed_event_count": processed_count, + "committed_row_count": committed_count, + "replay_count": replay_count, + "first_missing_index": first_missing_index, + "campaign_result_sha256": campaign_result_sha256, + "network_read_count": facts.network_read_count, + "network_write_count": facts.network_write_count, + "production_transport_call_count": ( + facts.production_transport_call_count + ), + "authenticated_endpoint_call_count": ( + facts.authenticated_endpoint_call_count + ), + "credential_read_count": facts.credential_read_count, + "production_write_count": facts.production_write_count, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": facts.real_account_data_used, + "operator_database_accessed": facts.operator_database_accessed, + "complete": False, + } + + def _truthful_failure_progress( + self, + *, + processed_count: int, + committed_count: int, + replay_count: int, + fallback_first_missing_index: int | None, + campaign_result_sha256: str | None = None, + ) -> dict[str, object]: + first_missing_index = fallback_first_missing_index + try: + progress, _ = self._scan_expected() + except ShadowCampaignRepositoryCorruption as exc: + if exc.first_invalid_event_index is not None: + first_missing_index = exc.first_invalid_event_index + except Exception: + pass + else: + first_missing_index = progress.first_missing_index + if campaign_result_sha256 is not None: + campaign_result_sha256 = progress.campaign_result_sha256 + return self._campaign_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=first_missing_index, + campaign_result_sha256=campaign_result_sha256, + ) + + def _raise_campaign_crash( + self, + crash_point: str, + *, + processed_count: int, + committed_count: int, + replay_count: int, + first_missing_index: int | None, + campaign_result_sha256: str | None = None, + ) -> None: + if crash_point in { + "before_corpus_initialization", + "after_campaign_spec_validation", + "before_first_event", + }: + progress = self._campaign_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=first_missing_index, + campaign_result_sha256=campaign_result_sha256, + ) + else: + progress = self._truthful_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + fallback_first_missing_index=first_missing_index, + campaign_result_sha256=campaign_result_sha256, + ) + raise ShadowCampaignInjectedCrash(crash_point, progress) + + def _repository_corruption_failure( + self, exc: ShadowCampaignRepositoryCorruption + ) -> ShadowCampaignOperationFailure: + return ShadowCampaignOperationFailure( + self._campaign_failure_progress( + processed_count=0, + committed_count=0, + replay_count=0, + first_missing_index=exc.first_invalid_event_index, + ), + failure_stage="repository_validation", + ) + + def _result( + self, + accumulator: _Accumulator, + *, + processed_count: int, + committed_count: int, + replay_count: int, + ) -> ShadowCampaignResult: + complete = accumulator.event_count == self.spec.event_count + campaign_hash = ( + accumulator.campaign_result_sha256() if complete else None + ) + return ShadowCampaignResult( + campaign_id=self.spec.campaign_id, + campaign_spec_sha256=self.spec.spec_sha256, + repository_sha=self.repository_sha, + total_event_count=self.spec.event_count, + processed_event_count=processed_count, + committed_event_count=committed_count, + completed_event_count=accumulator.event_count, + first_missing_index=None if complete else accumulator.event_count, + accepted_decision_count=accumulator.accepted_count, + blocked_decision_count=accumulator.blocked_count, + shadow_intent_count=accumulator.intent_count, + blocker_frequencies=accumulator.blocker_frequencies, + segment_decision_counts=accumulator.segment_decision_counts, + persisted_count=committed_count, + replay_count=replay_count, + segment_result_hashes=tuple(accumulator.segment_result_hashes), + window_result_hashes=tuple(accumulator.window_result_hashes), + event_sequence_sha256=accumulator.event_sequence_sha256, + final_decision_chain_sha256=accumulator.decision_chain_sha256, + campaign_result_sha256=campaign_hash, + safety_facts=ShadowSafetyFacts(0), + complete=complete, + ) + + def _execute( + self, + accumulator: _Accumulator, + *, + start_index: int, + stop_index: int, + crash_at: str | None, + crash_event_index: int | None, + ) -> ShadowCampaignResult: + if crash_at is not None and type(crash_at) is not str: + raise ValueError("campaign crash point must be exact text") + if crash_at is not None and crash_at not in ( + *CAMPAIGN_CRASH_POINTS, *RUNTIME_CRASH_POINTS + ): + raise ValueError("unknown historical shadow campaign crash point") + if crash_event_index is not None: + if ( + type(crash_event_index) is not int + or not start_index <= crash_event_index < stop_index + ): + raise ValueError("campaign crash_event_index is outside this operation") + campaign_crash = crash_at if crash_at in CAMPAIGN_CRASH_POINTS else None + runtime_crash = crash_at if crash_at in RUNTIME_CRASH_POINTS else None + processed_count = 0 + committed_count = 0 + replay_count = 0 + if campaign_crash == "before_corpus_initialization": + self._raise_campaign_crash( + campaign_crash, + processed_count=0, + committed_count=0, + replay_count=0, + first_missing_index=start_index, + ) + self._validate_spec() + if campaign_crash == "after_campaign_spec_validation": + self._raise_campaign_crash( + campaign_crash, + processed_count=0, + committed_count=0, + replay_count=0, + first_missing_index=start_index, + ) + if campaign_crash == "before_first_event" and start_index < stop_index: + self._raise_campaign_crash( + campaign_crash, + processed_count=0, + committed_count=0, + replay_count=0, + first_missing_index=start_index, + ) + for context in self._contexts(start_index, stop_index): + event = context.event + target_event = ( + crash_event_index is None + or crash_event_index == event.event_index + ) + try: + scenario = campaign_scenario_for_event(event, context.account) + summary = self._runtime.run_generated_fixture_event( + scenario, + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=context.shadow_run_id, + crash_at=runtime_crash if target_event else None, + ) + if summary.replayed: + replay_count += 1 + else: + committed_count += 1 + bundle = self.repository.load_bundle(context.shadow_run_id) + if bundle is None: + raise ValueError( + "campaign event committed no canonical shadow bundle" + ) + bundle = self._validate_bundle(context, bundle) + except ShadowCampaignPersistenceConflict as exc: + progress = self._truthful_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + fallback_first_missing_index=event.event_index, + ) + raise ShadowCampaignSpecConflict( + "campaign ID or logical event already has a different " + "authoritative bundle", + progress=progress, + failure_stage="event_execution", + ) from exc + except ShadowInjectedCrash as exc: + post_commit = isinstance(exc, ShadowPostCommitCrash) + if post_commit: + committed_count += 1 + fallback_missing = event.event_index + if post_commit: + fallback_missing = ( + None + if event.event_index + 1 == self.spec.event_count + else event.event_index + 1 + ) + exc.progress = self._truthful_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + fallback_first_missing_index=fallback_missing, + ) + raise + except Exception as exc: + progress = self._truthful_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + fallback_first_missing_index=event.event_index, + ) + raise ShadowCampaignOperationFailure( + progress, failure_stage="event_execution" + ) from exc + next_event_index = event.event_index + 1 + next_missing = ( + None if next_event_index == self.spec.event_count else next_event_index + ) + if campaign_crash == "after_event_bundle_commit" and target_event: + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=next_missing, + ) + if campaign_crash == "before_checkpoint_calculation" and target_event: + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=next_missing, + ) + accumulator.record_bundle(context, bundle) + processed_count += 1 + if campaign_crash == "after_checkpoint_calculation" and target_event: + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=next_missing, + ) + if self._is_segment_end(self.spec, event): + segment_target = crash_event_index is None or target_event + if campaign_crash == "before_segment_completion" and segment_target: + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=next_missing, + ) + accumulator.complete_segment(event) + if campaign_crash == "after_segment_completion" and segment_target: + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=next_missing, + ) + complete = accumulator.event_count == self.spec.event_count + if complete and campaign_crash == "before_final_campaign_hash": + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=None, + ) + final_hash: str | None = None + if complete: + inflight_hash = accumulator.campaign_result_sha256() + try: + final_progress, validated_accumulator = self._scan_expected() + except ShadowCampaignRepositoryCorruption as exc: + raise ShadowCampaignOperationFailure( + self._campaign_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=exc.first_invalid_event_index, + ), + failure_stage="final_repository_validation", + ) from exc + except ShadowCampaignPersistenceConflict as exc: + progress = self._truthful_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + fallback_first_missing_index=None, + ) + raise ShadowCampaignSpecConflict( + "campaign logical-event authority changed during execution", + progress=progress, + failure_stage="final_repository_validation", + ) from exc + except Exception as exc: + raise ShadowCampaignOperationFailure( + self._campaign_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=None, + ), + failure_stage="final_repository_validation", + ) from exc + if not final_progress.complete: + raise ShadowCampaignOperationFailure( + self._campaign_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=final_progress.first_missing_index, + ), + failure_stage="final_repository_validation", + ) + if final_progress.campaign_result_sha256 != inflight_hash: + raise ShadowCampaignOperationFailure( + self._campaign_failure_progress( + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=None, + ), + failure_stage="final_repository_validation", + ) + accumulator = validated_accumulator + final_hash = final_progress.campaign_result_sha256 + if complete and campaign_crash == "after_final_hash_before_cli_response": + self._raise_campaign_crash( + campaign_crash, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + first_missing_index=None, + campaign_result_sha256=final_hash, + ) + return self._result( + accumulator, + processed_count=processed_count, + committed_count=committed_count, + replay_count=replay_count, + ) + + def _reexecute_bundle( + self, context: _EventContext + ) -> dict[str, object]: + """Recompute one bundle in an isolated O(1)-memory authority.""" + + repository = MemoryShadowRepository() + runtime = ShadowAssuranceRuntime( + repository=repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: self._identity, + ) + with repository.campaign_authority(self.spec.campaign_id): + scenario = campaign_scenario_for_event( + context.event, context.account + ) + runtime.run_generated_fixture_event( + scenario, + campaign_event_hash=context.event.event_sha256, + timeframe=context.event.timeframe, + shadow_run_id=context.shadow_run_id, + ) + bundle = repository.load_bundle(context.shadow_run_id) + if bundle is None: + raise ShadowPersistenceConflict( + "independent campaign reexecution produced no bundle" + ) + return self._validate_bundle(context, bundle) + + def _scan_expected( + self, *, reexecute: bool = False + ) -> tuple[ShadowCampaignProgress, _Accumulator]: + self.repository.refresh_campaign_authority( + self.spec.campaign_id + ) + self._validate_spec() + accumulator = _Accumulator.create(self.spec, self.repository_sha) + scanned = 0 + valid = 0 + missing = 0 + first_missing: int | None = None + prefix_open = True + accepted = 0 + blocked = 0 + intents = 0 + blockers: dict[str, int] = {} + segment_decision_counts = { + segment.segment_id: {"accepted": 0, "blocked": 0} + for segment in self.spec.segments + } + segment_completion = { + segment.segment_id: 0 for segment in self.spec.segments + } + for context, bundle in self._loaded_contexts(): + scanned += 1 + if bundle is None: + missing += 1 + if first_missing is None: + first_missing = context.event.event_index + prefix_open = False + continue + bundle = self._validate_bundle(context, bundle) + if reexecute: + reexecuted = self._reexecute_bundle(context) + if bundle != reexecuted: + raise ShadowPersistenceConflict( + "persisted bundle differs from independent reexecution" + ) + valid += 1 + segment_completion[context.event.segment_id] += 1 + decision = bundle["decision"] + summary = bundle["summary"] + is_accepted = bool(decision["accepted"]) + accepted += int(is_accepted) + blocked += int(not is_accepted) + decision_kind = "accepted" if is_accepted else "blocked" + segment_decision_counts[context.event.segment_id][ + decision_kind + ] += 1 + intents += int(summary["shadow_intent_count"]) + for blocker in decision["blockers"]: + blockers[blocker] = blockers.get(blocker, 0) + 1 + if prefix_open: + accumulator.record_bundle(context, bundle) + if self._is_segment_end(self.spec, context.event): + accumulator.complete_segment(context.event) + complete = missing == 0 + campaign_hash = ( + accumulator.campaign_result_sha256() if complete else None + ) + progress = ShadowCampaignProgress( + campaign_id=self.spec.campaign_id, + campaign_spec_sha256=self.spec.spec_sha256, + repository_sha=self.repository_sha, + total_event_count=self.spec.event_count, + scanned_event_count=scanned, + completed_event_count=valid, + completed_prefix_count=accumulator.event_count, + missing_event_count=missing, + valid_bundle_count=valid, + invalid_bundle_count=0, + first_missing_index=first_missing, + accepted_decision_count=accepted, + blocked_decision_count=blocked, + shadow_intent_count=intents, + blocker_frequencies=blockers, + segment_completion=segment_completion, + segment_decision_counts=segment_decision_counts, + segment_result_hashes=tuple(accumulator.segment_result_hashes), + window_result_hashes=tuple(accumulator.window_result_hashes), + event_sequence_sha256=accumulator.event_sequence_sha256, + decision_chain_sha256=accumulator.decision_chain_sha256, + campaign_result_sha256=campaign_hash, + safety_facts=ShadowSafetyFacts(0), + complete=complete, + ) + return progress, accumulator + + def run( + self, + *, + stop_index: int | None = None, + crash_at: str | None = None, + crash_event_index: int | None = None, + ) -> ShadowCampaignResult: + stop = self._validate_stop(stop_index) + try: + with self.repository.campaign_authority(self.spec.campaign_id): + return self._execute( + _Accumulator.create(self.spec, self.repository_sha), + start_index=0, + stop_index=stop, + crash_at=crash_at, + crash_event_index=crash_event_index, + ) + except ShadowCampaignRepositoryCorruption as exc: + raise self._repository_corruption_failure(exc) from exc + except ShadowCampaignPersistenceConflict as exc: + raise ShadowCampaignSpecConflict( + "campaign has a conflicting persisted authority" + ) from exc + + def replay( + self, + *, + stop_index: int | None = None, + crash_at: str | None = None, + crash_event_index: int | None = None, + ) -> ShadowCampaignResult: + return self.run( + stop_index=stop_index, + crash_at=crash_at, + crash_event_index=crash_event_index, + ) + + def inspect(self) -> ShadowCampaignProgress: + try: + with self.repository.campaign_authority(self.spec.campaign_id): + return self._scan_expected()[0] + except ShadowCampaignRepositoryCorruption as exc: + raise self._repository_corruption_failure(exc) from exc + except ShadowCampaignPersistenceConflict as exc: + raise ShadowCampaignSpecConflict( + "campaign has a conflicting persisted authority" + ) from exc + + def verify(self) -> ShadowCampaignProgress: + """Reexecute every present event and compare its canonical bundle.""" + + try: + with self.repository.campaign_authority(self.spec.campaign_id): + return self._scan_expected(reexecute=True)[0] + except ShadowCampaignRepositoryCorruption as exc: + raise self._repository_corruption_failure(exc) from exc + except ShadowCampaignPersistenceConflict as exc: + raise ShadowCampaignSpecConflict( + "campaign has a conflicting persisted authority" + ) from exc + + def resume( + self, + *, + stop_index: int | None = None, + crash_at: str | None = None, + crash_event_index: int | None = None, + ) -> ShadowCampaignResult: + stop = self._validate_stop(stop_index) + try: + with self.repository.campaign_authority(self.spec.campaign_id): + progress, accumulator = self._scan_expected() + if progress.complete: + return self._result( + accumulator, + processed_count=0, + committed_count=0, + replay_count=0, + ) + start = progress.first_missing_index + if start is None: + raise AssertionError( + "incomplete campaign lacks a first missing event" + ) + if stop < start: + raise ValueError( + "resume stop_index precedes the first missing event" + ) + return self._execute( + accumulator, + start_index=start, + stop_index=stop, + crash_at=crash_at, + crash_event_index=crash_event_index, + ) + except ShadowCampaignRepositoryCorruption as exc: + raise self._repository_corruption_failure(exc) from exc + except ShadowCampaignPersistenceConflict as exc: + raise ShadowCampaignSpecConflict( + "campaign has a conflicting persisted authority" + ) from exc + + +__all__ = [ + "CAMPAIGN_CRASH_POINTS", + "SHADOW_CAMPAIGN_CHAIN_VERSION", + "SHADOW_CAMPAIGN_POLICY_CHAIN_VERSION", + "SHADOW_CAMPAIGN_RUNTIME_VERSION", + "ShadowCampaignInjectedCrash", + "ShadowCampaignOperationFailure", + "ShadowCampaignProgress", + "ShadowCampaignResult", + "ShadowCampaignRunResult", + "ShadowCampaignSpecConflict", + "ShadowCampaignRuntime", + "campaign_policy_chain_seed", + "campaign_policy_chain_sha256", + "campaign_policy_chain_step", + "campaign_policy_decision_sha256", + "campaign_policy_hash_projection", + "campaign_policy_input_sha256", + "campaign_policy_summary_sha256", + "campaign_scenario_for_event", + "derive_shadow_campaign_run_id", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier.py new file mode 100644 index 0000000..3594bf8 --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier.py @@ -0,0 +1,2314 @@ +"""Executable assurance verifier for the historical shadow campaign. + +The verifier derives every case result by executing deterministic code. It accepts +no caller-supplied pass counts, never opens a socket or database, and runs the full +25,920-event baseline plus an exact full replay through ``ShadowCampaignRuntime``. +Full-scale restart, gap, truncation, and lookahead cases preserve exact campaign +semantics; only scale-independent fault cases use explicit bounded specifications. +""" + +from __future__ import annotations + +from concurrent.futures import ThreadPoolExecutor +from copy import deepcopy +from dataclasses import replace +from datetime import timedelta +from decimal import Decimal +from pathlib import Path +import re +from threading import Barrier +from types import MappingProxyType +from typing import Callable, Mapping, Sequence + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .identity import validate_git_commit_sha +from .shadow_campaign_accounts import ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + SYNTHETIC_ACCOUNT_TIMELINE_STATES, + scenario_account_payload, + synthetic_account_snapshot_for_event, + synthetic_account_timeline_sha256, +) +from .shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + SHADOW_CAMPAIGN_GENERATOR_SHA256, + SHADOW_CAMPAIGN_GENERATOR_VERSION, + SHADOW_CAMPAIGN_VERSION, + calculate_shadow_campaign_corpus_hashes, + iter_shadow_campaign_events, +) +from .shadow_campaign_expected_hashes import ( + EXPECTED_CORPUS_GENERATOR_SHA256, + EXPECTED_FULL_CAMPAIGN_SPEC_SHA256, + EXPECTED_FULL_CORPUS_RESULT_SHA256, + EXPECTED_FULL_EVENT_SEQUENCE_SHA256, + EXPECTED_FULL_HASH_MANIFEST_SHA256, + EXPECTED_FULL_HASH_MANIFEST_VERSION, + EXPECTED_FULL_SEGMENT_HASHES, + EXPECTED_FULL_WINDOW_HASHES, + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + normalized_source_sha256, + validate_expected_corpus_results, + validate_expected_hash_manifest, + validate_generator_source_implementation, +) +from .shadow_campaign_models import ( + ShadowCampaignEvent, + ShadowCampaignSegment, + ShadowCampaignSpec, + SyntheticAccountTimelineSnapshot, + synthetic_timeline_snapshot_id, +) +from .shadow_repository import ( + MemoryShadowRepository, + ShadowMemoryStore, + ShadowPersistenceConflict, +) +from .shadow_campaign_verifier_runtime import ( + compare_actual_runtime_streams, + execute_actual_runtime_sequence, + execute_actual_same_run_id_conflict, + persist_actual_runtime_partition, +) +from .shadow_runtime import RUNTIME_CRASH_POINTS + + +CAMPAIGN_VERIFIER_VERSION = "phase8b-historical-shadow-campaign-verifier-v1" +POSTGRESQL_CAMPAIGN_NOT_EXECUTED = ( + "POSTGRESQL_CAMPAIGN_NOT_EXECUTED_DURING_ARTIFACT_GENERATION" +) + + +def _verifier_source_implementation_sha256() -> str: + source_root = Path(__file__).resolve().parent + source_hashes = { + name: normalized_source_sha256((source_root / name).read_bytes()) + for name in ( + "shadow_campaign_verifier.py", + "shadow_campaign_verifier_runtime.py", + ) + } + return sha256_payload( + { + "operation": "phase8b-shadow-verifier-source-implementation-v1", + "normalization": "utf8-bom-stripped-crlf-cr-to-lf-v1", + "source_sha256s": source_hashes, + } + ) + + +VERIFIER_IMPLEMENTATION_SHA256 = _verifier_source_implementation_sha256() + +_RESTART_CHECKPOINTS = ( + 1, + 17, + 288, + 2_016, + 4_320, + 8_640, + 12_960, + 17_280, + 21_600, + 25_919, +) +_EXPECTED_RUNTIME_CRASH_POINTS = ( + "market_snapshot_normalized", + "synthetic_account_validated", + "risk_evaluated", + "approval_created", + "manifest_created", + "before_decision_persist", + "after_decision_persist_before_summary", + "before_transaction_commit", + "after_transaction_commit_before_response", +) +_EXPECTED_CAMPAIGN_CRASH_POINTS = ( + "before_corpus_initialization", + "after_campaign_spec_validation", + "before_first_event", + "after_event_bundle_commit", + "before_checkpoint_calculation", + "after_checkpoint_calculation", + "before_segment_completion", + "after_segment_completion", + "before_final_campaign_hash", + "after_final_hash_before_cli_response", +) +_RESTART_CASES = ( + *(f"restart_checkpoint_{value}" for value in _RESTART_CHECKPOINTS), + "gap_at_beginning", + "gap_in_middle", + "multiple_gaps", + "later_segment_complete_earlier_missing", +) +_REPLAY_CASES = ("exact_full_replay",) +_MUTATION_CASES = ( + "price", + "timestamp", + "spread", + "instrument_metadata", + "synthetic_balance", + "kill_switch", + "pending_order", + "daily_loss", +) +_ANTI_LOOKAHEAD_CASES = ( + "future_last_event", + "next_segment", + "truncation_prefix", + "future_account_snapshot", + "expected_blocker_metadata", + "canonical_generation_order", +) +_CONCURRENCY_CASES = ( + "two_identical_campaign_starts", + "run_and_resume", + "two_resumes", + "different_event_partitions", + "same_campaign_id_different_spec", + "different_campaign_ids_same_spec_shape", + "inspect_reader_with_writer", + "verifier_with_writer", + "corrupted_scan_with_new_writer", + "post_commit_response_loss_with_retry", +) +_RUNTIME_CRASH_CASES = tuple( + f"runtime_{point}" for point in _EXPECTED_RUNTIME_CRASH_POINTS +) +_CAMPAIGN_CRASH_CASES = tuple( + f"campaign_{point}" for point in _EXPECTED_CAMPAIGN_CRASH_POINTS +) +CASE_CATALOGS = MappingProxyType( + { + "restart_results": _RESTART_CASES, + "replay_results": _REPLAY_CASES, + "mutation_results": _MUTATION_CASES, + "anti_lookahead_results": _ANTI_LOOKAHEAD_CASES, + "concurrency_results": _CONCURRENCY_CASES, + "runtime_crash_results": _RUNTIME_CRASH_CASES, + "campaign_crash_results": _CAMPAIGN_CRASH_CASES, + } +) + +_SPARSE_RESTART_GAPS = MappingProxyType({ + "gap_at_beginning": (0,), + "gap_in_middle": (12_960,), + "multiple_gaps": (288, 8_640, 21_600), + "later_segment_complete_earlier_missing": (2_159,), +}) + +# The schema maps each immutable case ID to (ordered fact keys, sorted check keys). +# This is deliberately independent of caller data: self-hashing an ad-hoc all-true +# mapping must never turn it into executable verifier evidence. +def _build_case_result_schemas(): + schemas: dict[str, tuple[tuple[str, ...], tuple[str, ...]]] = {} + + def register(cases, facts, checks): + fact_keys = tuple(facts) + check_keys = tuple(sorted(checks)) + for case in cases: + schemas[case] = (fact_keys, check_keys) + + register( + _RESTART_CASES[: len(_RESTART_CHECKPOINTS)], + ( + "checkpoint_event_count", + "resumed_suffix_event_count", + "first_missing_index", + "final_campaign_sha256", + ), + ( + "exact_full_scale", + "partial_run_processed_exact_checkpoint", + "partial_run_committed_exact_checkpoint", + "partial_run_replayed_nothing", + "partial_run_completed_exact_prefix", + "partial_store_has_exact_rows", + "partial_run_not_falsely_complete", + "fresh_process_scanned_full_campaign", + "fresh_process_reconstructed_from_deep_copied_bundles", + "fresh_process_detected_exact_prefix", + "first_missing_is_exact_checkpoint", + "full_suffix_processed", + "full_suffix_committed", + "no_duplicate_authority", + "resume_complete", + "final_hash_matches_uninterrupted", + ), + ) + register( + tuple(_SPARSE_RESTART_GAPS), + ("gaps", "first_missing_index", "resumed_from_event_index"), + ( + "exact_full_scale", + "full_repository_scanned", + "all_non_gap_rows_present", + "earliest_gap_selected", + "later_rows_not_treated_as_prefix", + "later_authority_was_present", + "resume_processed_full_remaining_range", + "only_gaps_committed", + "later_rows_replayed", + "resume_complete", + "no_duplicate_authority", + "final_hash_matches_uninterrupted", + ), + ) + register( + _REPLAY_CASES, + ("event_count", "campaign_result_sha256"), + ( + "replay_complete", "all_events_replayed", "no_new_commits", + "row_count_unchanged", "decision_chain_equal", + "segment_hashes_equal", "window_hashes_equal", + "segment_counts_equal", "campaign_hash_equal", + ), + ) + register( + _MUTATION_CASES, + ( + "mutation_index", "parent_campaign_sha256", + "parent_policy_chain_sha256", "mutated_policy_chain_sha256", + "mutation_lineage_sha256", + ), + ( + "actual_runtime_rows_complete", + "logical_event_identity_stable", + "event_data_hash_classified", + "event_data_prefix_invariant", + "policy_input_prefix_invariant", + "policy_decision_prefix_invariant", + "policy_summary_prefix_invariant", + "policy_chain_prefix_invariant", + "mutation_policy_input_diverged", + "mutation_policy_decision_diverged", + "mutation_policy_summary_diverged", + "canonical_policy_suffix_chain_diverged", + "temporal_input_not_future", + "same_logical_run_conflicts", + "single_authoritative_conflict_row", + "old_campaign_not_overwritten", + "explicit_parent_mutation_lineage_bound", + "no_mutated_campaign_authority_claimed", + "zero_authority", + ), + ) + anti = { + "future_last_event": ( + ("prefix_event_count",), + ( + "future_payload_changed", "all_prior_decisions_executed", + "all_prior_inputs_unchanged", "all_prior_decisions_unchanged", + "all_prior_chains_unchanged", "only_last_event_changed", + "account_inputs_unchanged", "both_full_streams_complete", + "exact_synthetic_account_state_catalog", + "exact_synthetic_account_ranges", + "all_accounts_explicitly_synthetic", + "expected_account_blockers_observed_in_effective_ranges", + "zero_authority", + ), + ), + "next_segment": ( + ("current_segment_event_count",), + ( + "next_segment_payload_changed", + "actual_current_segment_inputs_unchanged", + "actual_current_segment_decisions_unchanged", + "actual_current_segment_chain_unchanged", + "changed_next_event_diverged", "account_inputs_unchanged", + "both_segments_complete", "zero_authority", + ), + ), + "truncation_prefix": ( + ("event_count",), + ( + "full_stream_has_exact_campaign", + "truncated_stream_has_exact_prefix", + "prefix_event_hashes_equal", "prefix_account_hashes_equal", + "prefix_input_hashes_equal", "prefix_decision_hashes_equal", + "prefix_runtime_chains_equal", "zero_authority", + ), + ), + "future_account_snapshot": ( + ("historical_event_count",), + ( + "future_snapshot_changed", "historical_inputs_unchanged", + "historical_decisions_unchanged", "historical_chains_unchanged", + "historical_events_unchanged", "future_account_diverged", + "both_streams_complete", "zero_authority", + ), + ), + "expected_blocker_metadata": ( + ("runtime_snapshot_sha256",), + ( + "runtime_hash_unchanged", "metadata_hash_changed", + "actual_input_unchanged", "actual_decision_unchanged", + "actual_chain_unchanged", + ), + ), + "canonical_generation_order": ( + ("canonical_event_count",), + ( + "generation_order_changed", "canonical_order_equal", + "actual_inputs_equal", "actual_decisions_equal", + "actual_runtime_chains_equal", + ), + ), + } + for case, (facts, checks) in anti.items(): + register((case,), facts, checks) + + concurrency = { + "two_identical_campaign_starts": ( + "both_complete", "one_authority_per_event", + "final_hash_deterministic", "final_hash_matches_reference", + "inspect_complete", + ), + "run_and_resume": ( + "both_complete", "one_authority_per_event", + "final_hash_deterministic", "final_hash_matches_reference", + ), + "two_resumes": ( + "both_complete", "one_authority_per_event", + "final_hash_deterministic", "final_hash_matches_reference", + ), + "different_event_partitions": ( + "partitions_are_disjoint", "partitions_cover_exact_campaign", + "each_partition_persisted_exactly_once", "partition_authority_zero", + "final_complete", "final_hash_deterministic", + "one_authority_per_event", + ), + "same_campaign_id_different_spec": ( + "spec_hash_changed", "synchronized_before_campaign_authority", + "exactly_one_complete", "explicit_conflict", "single_spec_authority", + "winning_spec_is_one_contender", "winning_hash_matches_winning_spec", + "one_authority_per_event", "no_overwrite", + ), + "different_campaign_ids_same_spec_shape": ( + "both_complete", "lineage_isolated", "authority_sets_disjoint", + ), + "inspect_reader_with_writer": ( + "writer_complete", "reader_scanned_exact_campaign", + "reader_public_progress", "final_complete", + "final_hash_deterministic", "one_authority_per_event", + ), + "verifier_with_writer": ( + "writer_complete", "concurrent_verifier_executed", + "concurrent_verifier_canonical", "final_verifier_complete", + "final_hash_deterministic", "one_authority_per_event", + ), + "corrupted_scan_with_new_writer": ( + "corruption_rejected", "new_writer_rejected", + "corruption_public_progress_exact", + "writer_public_progress_exact", + "no_new_authority", "no_overwrite", + ), + "post_commit_response_loss_with_retry": ( + "response_loss_observed", "commit_precedes_retries", + "both_retries_replayed", "both_retries_completed_prefix", + "single_authoritative_row", "deterministic_retry_hash", + ), + } + for case, checks in concurrency.items(): + register((case,), (), checks) + + register( + _RUNTIME_CRASH_CASES, + ("crash_point", "lifecycle_ordinal", "committed_after_crash"), + ( + "injected_crash_observed", "exact_atomic_commitment", + "target_run_presence_exact", "inspect_valid_exact", + "inspect_prefix_exact", "inspect_missing_exact", + "inspect_first_missing_exact", "resume_complete", + "final_hash_matches", "exact_final_authority", + "no_production_write", + ), + ) + register( + _CAMPAIGN_CRASH_CASES, + ( + "crash_point", "lifecycle_ordinal", "failure_progress_sha256", + "committed_after_crash", + ), + ( + "injected_crash_observed", "exact_progress_schema", + "exact_processed_count", "exact_committed_count", + "exact_replay_count", "exact_first_missing", + "exact_failure_result_hash", "no_false_complete", + "public_safe_zero_authority", "committed_rows_canonical", + "resume_complete", "final_hash_matches", "exact_final_authority", + "no_production_write", + ), + ) + expected = frozenset(case for cases in CASE_CATALOGS.values() for case in cases) + if frozenset(schemas) != expected: + raise AssertionError("campaign case result schema catalog is incomplete") + return MappingProxyType(schemas) + + +CASE_RESULT_SCHEMAS = _build_case_result_schemas() + +# Fixed public vocabulary accepted from the shared fixture/preflight/risk policy. +# Evidence may contain a subset only; arbitrary caller-provided labels are rejected. +PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY = frozenset({ + "approval_notional", "ask_must_be_positive", "bid_must_be_positive", + "conflicting_account_classification", "conflicting_public_sources", + "crossed_bid_ask", "duplicate_public_response_rows", + "duplicate_synthetic_position", "excessive_reserved_notional", + "fixture_classification_mismatch", "fixture_market_data_forbidden", + "incomplete_public_response", "instrument_delisted", + "instrument_not_allowed", "instrument_not_live", + "insufficient_base_balance", "insufficient_quote_balance", + "kill_switch_not_armed", "malformed_account_snapshot", + "malformed_public_response", "market_data_currency_mismatch", + "market_data_future", "market_data_identity_mismatch", + "market_data_instrument_mismatch", "market_data_invalid", + "market_data_non_final", "market_data_price_missing", + "market_data_provider_mismatch", "market_data_quarantined", + "market_data_series_fields_mismatch", "market_data_stale", + "market_price_must_be_positive", "market_price_not_finite", + "market_snapshot_unavailable", "maximum_cancellations_per_minute", + "maximum_clock_skew", "maximum_daily_realized_loss", + "maximum_daily_submitted_notional", "maximum_drawdown", + "maximum_gross_exposure", "maximum_net_exposure", + "maximum_open_order_count", "maximum_order_notional", + "maximum_orders_per_minute", "maximum_position_notional_or_spot_short", + "maximum_reference_price_deviation", "maximum_run_duration", + "maximum_transport_failures", "missing_instrument_metadata", + "negative_synthetic_balance", "only_limit_orders_allowed", + "operational_classification_mismatch", "partial_public_response", + "public_market_future_timestamp", "public_network_connection_failure", + "public_network_rate_limit", "public_network_timeout", + "public_provider_error", "public_response_replay", + "quantity_above_maximum_after_rounding", + "quantity_below_minimum_after_rounding", "quantity_must_be_positive", + "quantity_not_finite", "reconciliation_blocked", "stale_account_snapshot", + "stale_cached_response", "stale_market_data", "stale_reconciliation", + "synthetic_derivative_exposure", "synthetic_permission_not_trade_enabled", + "synthetic_short_position", "unacknowledged_order_age", + "unknown_order_age", "wrong_instrument_type", "wrong_settlement_asset", +}) +VERIFIER_RESULT_KEYS = ( + "schema_version", + "operation", + "status", + "repository_sha", + "campaign_version", + "verifier_version", + "verifier_implementation_sha256", + "expected_hash_manifest_version", + "expected_hash_manifest_sha256", + "corpus_generator_version", + "corpus_spec_sha256", + "corpus_generator_sha256", + "corpus_generator_descriptor_sha256", + "generator_implementation_sha256", + "synthetic_account_timeline_sha256", + "timeframe", + "equivalent_duration_days", + "event_count", + "segment_count", + "event_sequence_sha256", + "corpus_campaign_result_sha256", + "corpus_segment_result_hashes", + "corpus_window_result_hashes", + "segment_result_hashes", + "window_result_hashes", + "accepted_decision_count", + "blocked_decision_count", + "segment_decision_counts", + "shadow_intent_count", + "blocker_frequencies", + "final_decision_chain_sha256", + "campaign_result_sha256", + "restart_results", + "restart_result_hashes", + "replay_results", + "replay_result_hashes", + "mutation_results", + "mutation_result_hashes", + "anti_lookahead_results", + "anti_lookahead_result_hashes", + "concurrency_results", + "concurrency_result_hashes", + "runtime_crash_results", + "runtime_crash_result_hashes", + "campaign_crash_results", + "campaign_crash_result_hashes", + "corrupted_bundle_rejections", + "zero_write_facts", + "postgresql_campaign", + "verifier_result_sha256", +) +_SHA256 = re.compile(r"^[0-9a-f]{64}$") +def _runtime_api(): + # Lazy import lets evidence/schema tooling import verifier constants while a + # campaign runtime change is being staged in the same source tree. + from .shadow_campaign_runtime import ( + CAMPAIGN_CRASH_POINTS, + ShadowCampaignInjectedCrash, + ShadowCampaignRuntime, + derive_shadow_campaign_run_id, + ) + + return ( + CAMPAIGN_CRASH_POINTS, + ShadowCampaignInjectedCrash, + ShadowCampaignRuntime, + derive_shadow_campaign_run_id, + ) + + +def _validate_case_facts(case: str, facts: Mapping[str, object]) -> None: + schema = CASE_RESULT_SCHEMAS.get(case) + if schema is None: + raise ValueError(f"unknown executable campaign verifier case: {case}") + fact_keys, _ = schema + if tuple(facts) != fact_keys: + raise ValueError(f"verifier case {case} fact schema/order is invalid") + for name, value in facts.items(): + if name == "gaps": + if ( + type(value) is not tuple + or not value + or any(type(item) is not int or not 0 <= item < 25_920 for item in value) + or tuple(sorted(set(value))) != value + ): + raise ValueError(f"verifier case {case} gaps are invalid") + elif name == "crash_point": + if type(value) is not str or not value: + raise ValueError(f"verifier case {case} crash point is invalid") + elif name.endswith("sha256"): + if type(value) is not str or _SHA256.fullmatch(value) is None: + raise ValueError(f"verifier case {case} contains a non-digest fact") + elif type(value) is not int or not 0 <= value <= 25_920: + raise ValueError(f"verifier case {case} contains an invalid integer fact") + + if case.startswith("restart_checkpoint_"): + checkpoint = int(case.removeprefix("restart_checkpoint_")) + if ( + facts["checkpoint_event_count"] != checkpoint + or facts["resumed_suffix_event_count"] != 25_920 - checkpoint + or facts["first_missing_index"] != checkpoint + ): + raise ValueError("restart checkpoint facts are inconsistent") + elif case in _SPARSE_RESTART_GAPS: + gaps = _SPARSE_RESTART_GAPS[case] + if ( + facts["gaps"] != gaps + or facts["first_missing_index"] != min(gaps) + or facts["resumed_from_event_index"] != min(gaps) + ): + raise ValueError("sparse restart facts are inconsistent") + elif case == "exact_full_replay" and facts["event_count"] != 25_920: + raise ValueError("full replay event count is invalid") + elif case in _MUTATION_CASES: + if ( + facts["mutation_index"] != 20 + or facts["parent_policy_chain_sha256"] == facts["mutated_policy_chain_sha256"] + ): + raise ValueError("mutation lineage facts are invalid") + elif case in _ANTI_LOOKAHEAD_CASES: + expected = { + "future_last_event": ("prefix_event_count", 25_919), + "next_segment": ("current_segment_event_count", 2_160), + "truncation_prefix": ("event_count", 777), + "future_account_snapshot": ("historical_event_count", 2_160), + "canonical_generation_order": ("canonical_event_count", 288), + }.get(case) + if expected is not None and facts[expected[0]] != expected[1]: + raise ValueError("anti-lookahead scale fact is invalid") + elif case in _RUNTIME_CRASH_CASES: + point = case.removeprefix("runtime_") + if ( + facts["crash_point"] != point + or facts["lifecycle_ordinal"] != _EXPECTED_RUNTIME_CRASH_POINTS.index(point) + or facts["committed_after_crash"] not in (0, 1) + ): + raise ValueError("runtime crash facts are invalid") + elif case in _CAMPAIGN_CRASH_CASES: + point = case.removeprefix("campaign_") + if ( + facts["crash_point"] != point + or facts["lifecycle_ordinal"] != _EXPECTED_CAMPAIGN_CRASH_POINTS.index(point) + or facts["committed_after_crash"] > 12 + ): + raise ValueError("campaign crash facts are invalid") + + +def _case_result(case: str, **facts: object) -> dict[str, object]: + checks = facts.pop("checks", None) + _validate_case_facts(case, facts) + _, expected_check_keys = CASE_RESULT_SCHEMAS[case] + if ( + type(checks) is not dict + or tuple(sorted(checks)) != expected_check_keys + or any(type(key) is not str for key in checks) + or any(type(value) is not bool for value in checks.values()) + ): + raise AssertionError(f"verifier case {case} check catalog/types are invalid") + if not all(checks.values()): + failed = tuple(key for key, value in checks.items() if not value) + raise AssertionError(f"verifier case {case} failed checks: {failed}") + core: dict[str, object] = { + "case": case, + **facts, + "checks": {key: checks[key] for key in expected_check_keys}, + "passed": True, + } + return {**core, "result_sha256": sha256_payload(core)} + + +def _validate_exact_verifier_tree( + value: object, + *, + active_container_ids: set[int] | None = None, +) -> None: + """Reject behavior-bearing verifier subclasses before traversing the tree.""" + + value_type = type(value) + if value_type in (str, int, bool) or value is None: + return + if value_type not in (dict, tuple): + raise TypeError("verifier results must use exact canonical value types") + active = active_container_ids if active_container_ids is not None else set() + identity = id(value) + if identity in active: + raise ValueError("verifier results must not contain cycles") + active.add(identity) + try: + if value_type is dict: + for item_key, item in value.items(): + if type(item_key) is not str: + raise TypeError("verifier result keys must be exact text") + _validate_exact_verifier_tree(item, active_container_ids=active) + else: + for item in value: + _validate_exact_verifier_tree(item, active_container_ids=active) + finally: + active.remove(identity) + + +def passed_campaign_case_count(verifier: Mapping[str, object], key: str) -> int: + """Validate one exact executable catalog and return its fixed pass count.""" + + if type(key) is not str: + raise TypeError("verifier case catalog key must be exact text") + if type(verifier) is not dict: + raise TypeError("verifier result must be an exact mapping") + _validate_exact_verifier_tree(verifier) + if key not in CASE_CATALOGS: + raise ValueError(f"unknown campaign verifier case catalog: {key}") + if key not in verifier: + raise ValueError(f"{key} is missing from the verifier result") + values = verifier[key] + if type(values) is not tuple: + raise ValueError(f"{key} must contain executable verifier cases") + _validate_exact_verifier_tree(values) + expected_cases = CASE_CATALOGS[key] + observed_cases = tuple(value["case"] for value in values) + if observed_cases != expected_cases: + raise ValueError(f"{key} differs from the exact ordered case catalog") + for value in values: + case = value["case"] + fact_keys, expected_check_keys = CASE_RESULT_SCHEMAS[case] + expected_keys = ( + "case", *fact_keys, "checks", "passed", "result_sha256" + ) + if tuple(value) != expected_keys: + raise ValueError(f"{key} contains an invalid nested key schema/order") + facts = {name: value[name] for name in fact_keys} + _validate_case_facts(case, facts) + checks = value["checks"] + if ( + type(checks) is not dict + or tuple(checks) != expected_check_keys + or any(item is not True for item in checks.values()) + ): + raise ValueError(f"{key} contains unknown, caller-fed, or failed checks") + if value["passed"] is not True: + raise ValueError(f"{key} contains a non-passing case") + result_hash = value.get("result_sha256") + if type(result_hash) is not str or _SHA256.fullmatch(result_hash) is None: + raise ValueError(f"{key} contains a non-digest result hash") + core = { + name: item + for name, item in value.items() + if name != "result_sha256" + } + if result_hash != sha256_payload(core): + raise ValueError(f"{key} contains a forged or stale result hash") + return len(values) + + +def _bounded_inputs( + *, + campaign_id: str = "phase8b-shadow-verifier-bounded-v1", + segment_count: int = 2, + events_per_segment: int = 6, +) -> tuple[ShadowCampaignSpec, tuple[SyntheticAccountTimelineSnapshot, ...]]: + if not 1 <= segment_count <= 12 or events_per_segment <= 0: + raise ValueError("bounded verifier dimensions are invalid") + segments: list[ShadowCampaignSegment] = [] + timeline: list[SyntheticAccountTimelineSnapshot] = [] + for index in range(segment_count): + source_segment = FULL_90D_CAMPAIGN_SPEC.segments[index] + segment = replace( + source_segment, + segment_id=f"verifier-segment-{index + 1:02d}-{source_segment.regime}", + segment_index=index, + start_event_index=index * events_per_segment, + event_count=events_per_segment, + ) + segments.append(segment) + source_snapshot = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[index] + payload = scenario_account_payload(source_snapshot) + start = index * events_per_segment + end = start + events_per_segment + snapshot_id = synthetic_timeline_snapshot_id( + state_name=source_snapshot.state_name, + effective_start_event_index=start, + effective_end_event_index=end, + runtime_payload=payload, + ) + timeline.append( + SyntheticAccountTimelineSnapshot( + snapshot_id=snapshot_id, + state_name=source_snapshot.state_name, + effective_start_event_index=start, + effective_end_event_index=end, + runtime_payload=payload, + expected_account_blockers=source_snapshot.expected_account_blockers, + ) + ) + event_count = segment_count * events_per_segment + spec = replace( + FULL_90D_CAMPAIGN_SPEC, + campaign_id=campaign_id, + event_count=event_count, + events_per_segment=events_per_segment, + equivalent_duration_days=1, + segments=tuple(segments), + window_labels=("bounded",), + window_event_counts=(event_count,), + ) + return spec, tuple(timeline) + + +def _run_ids( + spec: ShadowCampaignSpec, + timeline: tuple[SyntheticAccountTimelineSnapshot, ...], + repository_sha: str, +): + _, _, _, derive_run_id = _runtime_api() + result = [] + for event in iter_shadow_campaign_events(spec): + snapshot = synthetic_account_snapshot_for_event(event.event_index, timeline) + result.append( + derive_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=snapshot.snapshot_sha256, + repository_sha=repository_sha, + campaign_spec_sha256=spec.spec_sha256, + ) + ) + return tuple(result) + + +def _clone_repository( + source: MemoryShadowRepository, + run_ids: Sequence, +) -> MemoryShadowRepository: + with source.store.lock: + bundles = { + run_id: deepcopy(source.store.bundles[run_id]) + for run_id in run_ids + if run_id in source.store.bundles + } + # Caches are never copied: the fresh repository must rebuild campaign + # authority exclusively from canonical deep-copied bundles. + return MemoryShadowRepository(ShadowMemoryStore(bundles=bundles)) + + +def _bounded_baseline(repository_sha: str): + _, _, runtime_type, _ = _runtime_api() + spec, timeline = _bounded_inputs() + repository = MemoryShadowRepository() + runtime = runtime_type( + repository, repository_sha, spec=spec, account_timeline=timeline + ) + result = runtime.run() + if not result.complete or result.completed_event_count != spec.event_count: + raise AssertionError("bounded campaign baseline did not complete") + return spec, timeline, repository, result, _run_ids(spec, timeline, repository_sha) + + +def _run_restart_matrix(repository_sha: str) -> tuple[dict[str, object], ...]: + """Execute all exact full-size restart and sparse-gap recoveries.""" + + _, _, runtime_type, _ = _runtime_api() + spec = FULL_90D_CAMPAIGN_SPEC + timeline = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + baseline_repository = MemoryShadowRepository() + baseline = runtime_type( + baseline_repository, + repository_sha, + spec=spec, + account_timeline=timeline, + ).run() + run_ids = _run_ids(spec, timeline, repository_sha) + if ( + spec.event_count != 25_920 + or baseline.completed_event_count != 25_920 + or len(run_ids) != 25_920 + or baseline_repository.row_counts()["audit.run_manifests"] != 25_920 + ): + raise AssertionError("restart baseline is not the exact 25,920-event campaign") + + results: list[dict[str, object]] = [] + for checkpoint in _RESTART_CHECKPOINTS: + partial_repository = MemoryShadowRepository() + partial = runtime_type( + partial_repository, + repository_sha, + spec=spec, + account_timeline=timeline, + ).run(stop_index=checkpoint) + partial_row_count = partial_repository.row_counts()["audit.run_manifests"] + repository = _clone_repository( + partial_repository, run_ids[:checkpoint] + ) + with partial_repository.store.lock, repository.store.lock: + fresh_process_reconstructed = ( + repository.store is not partial_repository.store + and repository.store.campaign_spec_bindings == {} + and repository.store.campaign_locks == {} + and tuple(repository.store.bundles) == run_ids[:checkpoint] + and all( + repository.store.bundles[run_id] + is not partial_repository.store.bundles[run_id] + for run_id in run_ids[:checkpoint] + ) + ) + runtime = runtime_type( + repository, + repository_sha, + spec=spec, + account_timeline=timeline, + ) + before = runtime.inspect() + resumed = runtime.resume() + suffix_count = spec.event_count - checkpoint + results.append( + _case_result( + f"restart_checkpoint_{checkpoint}", + checkpoint_event_count=checkpoint, + resumed_suffix_event_count=suffix_count, + first_missing_index=before.first_missing_index, + final_campaign_sha256=resumed.campaign_result_sha256, + checks={ + "exact_full_scale": spec.event_count == 25_920, + "partial_run_processed_exact_checkpoint": ( + partial.processed_event_count == checkpoint + ), + "partial_run_committed_exact_checkpoint": ( + partial.committed_event_count == checkpoint + ), + "partial_run_replayed_nothing": partial.replay_count == 0, + "partial_run_completed_exact_prefix": ( + partial.completed_event_count == checkpoint + ), + "partial_store_has_exact_rows": ( + partial_row_count == checkpoint + ), + "partial_run_not_falsely_complete": ( + partial.complete is False + and partial.campaign_result_sha256 is None + and partial.first_missing_index == checkpoint + ), + "fresh_process_scanned_full_campaign": ( + before.scanned_event_count == 25_920 + ), + "fresh_process_reconstructed_from_deep_copied_bundles": ( + fresh_process_reconstructed + ), + "fresh_process_detected_exact_prefix": ( + before.completed_prefix_count == checkpoint + ), + "first_missing_is_exact_checkpoint": ( + before.first_missing_index == checkpoint + ), + "full_suffix_processed": ( + resumed.processed_event_count == suffix_count + ), + "full_suffix_committed": ( + resumed.committed_event_count == suffix_count + ), + "no_duplicate_authority": ( + repository.row_counts()["audit.run_manifests"] == 25_920 + ), + "resume_complete": resumed.complete, + "final_hash_matches_uninterrupted": ( + resumed.campaign_result_sha256 + == baseline.campaign_result_sha256 + ), + }, + ) + ) + del runtime, repository, partial_repository + + sparse = tuple(_SPARSE_RESTART_GAPS.items()) + for case, gaps in sparse: + gap_set = frozenset(gaps) + kept = tuple( + run_id + for index, run_id in enumerate(run_ids) + if index not in gap_set + ) + repository = _clone_repository(baseline_repository, kept) + runtime = runtime_type( + repository, + repository_sha, + spec=spec, + account_timeline=timeline, + ) + before = runtime.inspect() + resumed = runtime.resume() + expected_replays = spec.event_count - min(gaps) - len(gaps) + results.append( + _case_result( + case, + gaps=gaps, + first_missing_index=before.first_missing_index, + resumed_from_event_index=min(gaps), + checks={ + "exact_full_scale": spec.event_count == 25_920, + "full_repository_scanned": before.scanned_event_count == 25_920, + "all_non_gap_rows_present": ( + before.valid_bundle_count == 25_920 - len(gaps) + ), + "earliest_gap_selected": before.first_missing_index == min(gaps), + "later_rows_not_treated_as_prefix": ( + before.completed_prefix_count == min(gaps) + ), + "later_authority_was_present": ( + before.valid_bundle_count > before.completed_prefix_count + ), + "resume_processed_full_remaining_range": ( + resumed.processed_event_count + == 25_920 - min(gaps) + ), + "only_gaps_committed": ( + resumed.committed_event_count == len(gaps) + ), + "later_rows_replayed": resumed.replay_count == expected_replays, + "resume_complete": resumed.complete, + "no_duplicate_authority": ( + repository.row_counts()["audit.run_manifests"] == 25_920 + ), + "final_hash_matches_uninterrupted": ( + resumed.campaign_result_sha256 + == baseline.campaign_result_sha256 + ), + }, + ) + ) + del runtime, repository, kept + del baseline_repository + return tuple(results) + + +def _snapshot_from_payload( + source: SyntheticAccountTimelineSnapshot, + payload: Mapping[str, object], +) -> SyntheticAccountTimelineSnapshot: + snapshot_id = synthetic_timeline_snapshot_id( + state_name=source.state_name, + effective_start_event_index=source.effective_start_event_index, + effective_end_event_index=source.effective_end_event_index, + runtime_payload=payload, + ) + return SyntheticAccountTimelineSnapshot( + snapshot_id=snapshot_id, + state_name=source.state_name, + effective_start_event_index=source.effective_start_event_index, + effective_end_event_index=source.effective_end_event_index, + runtime_payload=payload, + expected_account_blockers=source.expected_account_blockers, + ) + + +def _input_chain( + events: Sequence[ShadowCampaignEvent], + base_snapshot: SyntheticAccountTimelineSnapshot, + *, + repository_sha: str, + mutation_index: int, + event_override: ShadowCampaignEvent | None = None, + account_override: SyntheticAccountTimelineSnapshot | None = None, +): + selected_events = tuple( + event_override + if event_override is not None and event.event_index == mutation_index + else event + for event in events + ) + selected_accounts = tuple( + account_override + if account_override is not None and event.event_index == mutation_index + else base_snapshot + for event in events + ) + return execute_actual_runtime_sequence( + selected_events, + selected_accounts, + repository_sha=repository_sha, + ) + + +def _event_values(event: ShadowCampaignEvent) -> dict[str, object]: + return { + name: getattr(event, name) for name in event.__dataclass_fields__ + } + + +def _run_mutation_matrix( + repository_sha: str, + *, + parent_campaign_sha256: str, +) -> tuple[dict[str, object], ...]: + if type(parent_campaign_sha256) is not str or _SHA256.fullmatch( + parent_campaign_sha256 + ) is None: + raise ValueError("parent campaign hash must be a lowercase SHA-256 digest") + events = tuple(iter_shadow_campaign_events(stop_index=48)) + mutation_index = 20 + original = events[mutation_index] + base_snapshot = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + base = _input_chain( + events, + base_snapshot, + repository_sha=repository_sha, + mutation_index=mutation_index, + ) + mutations: list[ + tuple[ + str, + ShadowCampaignEvent | None, + SyntheticAccountTimelineSnapshot | None, + ] + ] = [] + + values = _event_values(original) + new_close = original.close_price + original.tick_size + values.update(close_price=new_close, high_price=max(original.high_price, new_close)) + mutations.append(("price", ShadowCampaignEvent(**values), None)) + values = _event_values(original) + values["market_timestamp_utc"] = original.market_timestamp_utc - timedelta(minutes=5) + mutations.append(("timestamp", ShadowCampaignEvent(**values), None)) + values = _event_values(original) + values["ask_price"] = original.ask_price + original.tick_size + if original.direction == "long": + values["limit_price"] = values["ask_price"] + mutations.append(("spread", ShadowCampaignEvent(**values), None)) + values = _event_values(original) + values["instrument_status"] = "suspended" + mutations.append(("instrument_metadata", ShadowCampaignEvent(**values), None)) + + payload = scenario_account_payload(base_snapshot) + payload["balances"][0]["available"] = Decimal("9000") + mutations.append(("synthetic_balance", None, _snapshot_from_payload(base_snapshot, payload))) + payload = scenario_account_payload(base_snapshot) + payload["kill_switch_active"] = True + mutations.append(("kill_switch", None, _snapshot_from_payload(base_snapshot, payload))) + payload = scenario_account_payload(base_snapshot) + payload["pending_orders"] = [{ + "instrument": "BTC-USDT", + "side": "buy", + "quantity": Decimal("0.001"), + "reserved_notional": Decimal("50"), + }] + payload["reserved_notional"] = Decimal("50") + payload["balances"][0] = { + "asset": "USDT", + "total": Decimal("10000"), + "available": Decimal("9950"), + "reserved": Decimal("50"), + } + mutations.append(("pending_order", None, _snapshot_from_payload(base_snapshot, payload))) + payload = scenario_account_payload(base_snapshot) + payload["daily_realized_pnl"] = Decimal("-510") + payload["current_equity"] = Decimal("9490") + mutations.append(("daily_loss", None, _snapshot_from_payload(base_snapshot, payload))) + + results: list[dict[str, object]] = [] + lineage_ids: set[str] = set() + for case, event_override, account_override in mutations: + selected_event = event_override or original + selected_account = account_override or base_snapshot + is_event_mutation = event_override is not None + mutated = _input_chain( + events, + base_snapshot, + repository_sha=repository_sha, + mutation_index=mutation_index, + event_override=event_override, + account_override=account_override, + ) + conflict = execute_actual_same_run_id_conflict( + original, + base_snapshot, + event_override or original, + account_override or base_snapshot, + repository_sha=repository_sha, + ) + lineage_payload = { + "operation": "phase8b-shadow-campaign-mutation-lineage-v2", + "mutation_case": case, + "parent_campaign_sha256": parent_campaign_sha256, + "parent_policy_chain_sha256": base.final_policy_chain_sha256, + "mutated_policy_chain_sha256": mutated.final_policy_chain_sha256, + "parent_campaign_id": original.campaign_id, + "parent_campaign_spec_sha256": original.campaign_spec_sha256, + "mutation_event_sha256": selected_event.event_sha256, + "mutation_account_sha256": selected_account.snapshot_sha256, + } + lineage_sha256 = sha256_payload(lineage_payload) + lineage_ids.add(lineage_sha256) + results.append( + _case_result( + case, + mutation_index=mutation_index, + parent_campaign_sha256=parent_campaign_sha256, + parent_policy_chain_sha256=base.final_policy_chain_sha256, + mutated_policy_chain_sha256=mutated.final_policy_chain_sha256, + mutation_lineage_sha256=lineage_sha256, + checks={ + "actual_runtime_rows_complete": base.row_count == mutated.row_count == len(events), + "logical_event_identity_stable": selected_event.event_id == original.event_id, + "event_data_hash_classified": ( + selected_event.event_sha256 != original.event_sha256 + ) is is_event_mutation, + "event_data_prefix_invariant": mutated.event_sha256s[:mutation_index] == base.event_sha256s[:mutation_index], + "policy_input_prefix_invariant": mutated.policy_input_sha256s[:mutation_index] == base.policy_input_sha256s[:mutation_index], + "policy_decision_prefix_invariant": mutated.policy_decision_sha256s[:mutation_index] == base.policy_decision_sha256s[:mutation_index], + "policy_summary_prefix_invariant": mutated.policy_summary_sha256s[:mutation_index] == base.policy_summary_sha256s[:mutation_index], + "policy_chain_prefix_invariant": mutated.policy_chain_sha256s[:mutation_index] == base.policy_chain_sha256s[:mutation_index], + "mutation_policy_input_diverged": mutated.policy_input_sha256s[mutation_index] != base.policy_input_sha256s[mutation_index], + "mutation_policy_decision_diverged": mutated.policy_decision_sha256s[mutation_index] != base.policy_decision_sha256s[mutation_index], + "mutation_policy_summary_diverged": mutated.policy_summary_sha256s[mutation_index] != base.policy_summary_sha256s[mutation_index], + "canonical_policy_suffix_chain_diverged": all( + left != right + for left, right in zip( + mutated.policy_chain_sha256s[mutation_index:], + base.policy_chain_sha256s[mutation_index:], + ) + ), + "temporal_input_not_future": selected_event.market_timestamp_utc <= selected_event.scheduled_at_utc, + "same_logical_run_conflicts": conflict["explicit_conflict"] is True, + "single_authoritative_conflict_row": conflict["single_authoritative_row"] is True, + "old_campaign_not_overwritten": conflict["original_bundle_preserved"] is True, + "explicit_parent_mutation_lineage_bound": ( + lineage_sha256 == sha256_payload(lineage_payload) + and lineage_payload["parent_campaign_sha256"] + == parent_campaign_sha256 + and lineage_payload["parent_campaign_id"] + == original.campaign_id + and lineage_payload["parent_campaign_spec_sha256"] + == original.campaign_spec_sha256 + ), + "no_mutated_campaign_authority_claimed": ( + "mutation_campaign_id" not in lineage_payload + and "mutated_campaign_sha256" not in lineage_payload + ), + "zero_authority": ( + not any(base.zero_authority_facts.values()) + and not any(mutated.zero_authority_facts.values()) + ), + }, + ) + ) + if len(lineage_ids) != len(_MUTATION_CASES): + raise AssertionError("mutation cases do not have distinct lineage") + return tuple(results) + + +def _ordered_event_hash(events: Sequence[ShadowCampaignEvent]) -> str: + chain = sha256_payload({"operation": "verifier-antileak-events-v1"}) + for event in events: + chain = sha256_payload( + {"prior_sha256": chain, "event_sha256": event.event_sha256} + ) + return chain + + +def _run_anti_lookahead_matrix( + repository_sha: str, +) -> tuple[dict[str, object], ...]: + """Prove prefix independence through actual shared-runtime decisions.""" + + def stream_inputs( + events, + overrides: Mapping[int, SyntheticAccountTimelineSnapshot] | None = None, + ): + overrides = {} if overrides is None else overrides + for event in events: + yield ( + event, + overrides.get( + event.event_index, + synthetic_account_snapshot_for_event(event.event_index), + ), + ) + + def execute(events, overrides=None): + values = tuple(events) + accounts = tuple(account for _, account in stream_inputs(values, overrides)) + return execute_actual_runtime_sequence( + values, + accounts, + repository_sha=repository_sha, + ) + + results: list[dict[str, object]] = [] + last = next(iter_shadow_campaign_events(start_index=25_919)) + values = _event_values(last) + values["volume"] = last.volume + Decimal("1") + changed_last = ShadowCampaignEvent(**values) + + def with_changed_last(): + for event in iter_shadow_campaign_events(): + yield changed_last if event.event_index == changed_last.event_index else event + + full_last = compare_actual_runtime_streams( + stream_inputs(iter_shadow_campaign_events()), + stream_inputs(with_changed_last()), + repository_sha=repository_sha, + ) + observed_by_account = full_last[ + "left_observed_blockers_by_account_runtime_sha256" + ] + results.append(_case_result("future_last_event", prefix_event_count=25_919, checks={ + "future_payload_changed": changed_last.event_sha256 != last.event_sha256, + "all_prior_decisions_executed": full_last["shared_event_count"] == 25_920, + "all_prior_inputs_unchanged": full_last["first_input_divergence_index"] is None, + "all_prior_decisions_unchanged": full_last["first_decision_divergence_index"] is None, + "all_prior_chains_unchanged": full_last["first_chain_divergence_index"] == 25_919, + "only_last_event_changed": full_last["first_event_divergence_index"] == 25_919, + "account_inputs_unchanged": full_last["first_account_divergence_index"] is None, + "both_full_streams_complete": full_last["left_event_count"] == full_last["right_event_count"] == 25_920, + "exact_synthetic_account_state_catalog": tuple( + snapshot.state_name for snapshot in FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ) == SYNTHETIC_ACCOUNT_TIMELINE_STATES, + "exact_synthetic_account_ranges": len(FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE) == 12 and all( + snapshot.effective_start_event_index == segment.start_event_index + and snapshot.effective_end_event_index == segment.end_event_index_exclusive + for snapshot, segment in zip( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, FULL_90D_CAMPAIGN_SPEC.segments + ) + ), + "all_accounts_explicitly_synthetic": all( + snapshot.synthetic_account is True + and snapshot.runtime_payload.get("synthetic_account") is True + for snapshot in FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ), + "expected_account_blockers_observed_in_effective_ranges": all( + set(snapshot.expected_account_blockers).issubset( + observed_by_account.get(snapshot.runtime_payload_sha256, ()) + ) + for snapshot in FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ), + "zero_authority": full_last["zero_authority"] is True, + })) + + next_event = next(iter_shadow_campaign_events(start_index=2_160, stop_index=2_161)) + values = _event_values(next_event) + values["volume"] = next_event.volume + Decimal("1") + changed_next = ShadowCampaignEvent(**values) + + def segment_through(replacement): + for event in iter_shadow_campaign_events(stop_index=2_161): + yield replacement if event.event_index == replacement.event_index else event + + next_segment = compare_actual_runtime_streams( + stream_inputs(iter_shadow_campaign_events(stop_index=2_161)), + stream_inputs(segment_through(changed_next)), + repository_sha=repository_sha, + ) + results.append(_case_result("next_segment", current_segment_event_count=2_160, checks={ + "next_segment_payload_changed": changed_next.event_sha256 != next_event.event_sha256, + "actual_current_segment_inputs_unchanged": next_segment["first_input_divergence_index"] is None, + "actual_current_segment_decisions_unchanged": next_segment["first_decision_divergence_index"] is None, + "actual_current_segment_chain_unchanged": next_segment["first_chain_divergence_index"] == 2_160, + "changed_next_event_diverged": next_segment["first_event_divergence_index"] == 2_160, + "account_inputs_unchanged": next_segment["first_account_divergence_index"] is None, + "both_segments_complete": next_segment["left_event_count"] == next_segment["right_event_count"] == 2_161, + "zero_authority": next_segment["zero_authority"] is True, + })) + + truncation = compare_actual_runtime_streams( + stream_inputs(iter_shadow_campaign_events()), + stream_inputs(iter_shadow_campaign_events(stop_index=777)), + repository_sha=repository_sha, + ) + results.append(_case_result("truncation_prefix", event_count=777, checks={ + "full_stream_has_exact_campaign": truncation["left_event_count"] == 25_920, + "truncated_stream_has_exact_prefix": truncation["right_event_count"] == truncation["shared_event_count"] == 777, + "prefix_event_hashes_equal": truncation["first_event_divergence_index"] is None, + "prefix_account_hashes_equal": truncation["first_account_divergence_index"] is None, + "prefix_input_hashes_equal": truncation["first_input_divergence_index"] is None, + "prefix_decision_hashes_equal": truncation["first_decision_divergence_index"] is None, + "prefix_runtime_chains_equal": truncation["first_chain_divergence_index"] is None, + "zero_authority": truncation["zero_authority"] is True, + })) + + future_event = next(iter_shadow_campaign_events(start_index=2_160, stop_index=2_161)) + future = synthetic_account_snapshot_for_event(future_event.event_index) + payload = scenario_account_payload(future) + payload["current_equity"] = Decimal("9999") + changed_future = _snapshot_from_payload(future, payload) + future_account = compare_actual_runtime_streams( + stream_inputs(iter_shadow_campaign_events(stop_index=2_161)), + stream_inputs( + iter_shadow_campaign_events(stop_index=2_161), + {future_event.event_index: changed_future}, + ), + repository_sha=repository_sha, + ) + results.append(_case_result("future_account_snapshot", historical_event_count=2_160, checks={ + "future_snapshot_changed": changed_future.snapshot_sha256 != future.snapshot_sha256, + "historical_inputs_unchanged": future_account["first_input_divergence_index"] == 2_160, + "historical_decisions_unchanged": future_account["first_decision_divergence_index"] == 2_160, + "historical_chains_unchanged": future_account["first_chain_divergence_index"] == 2_160, + "historical_events_unchanged": future_account["first_event_divergence_index"] is None, + "future_account_diverged": future_account["first_account_divergence_index"] == 2_160, + "both_streams_complete": future_account["left_event_count"] == future_account["right_event_count"] == 2_161, + "zero_authority": future_account["zero_authority"] is True, + })) + + historical = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + changed_expectation = replace(historical, expected_account_blockers=("expected_only",)) + expectation_event = next(iter_shadow_campaign_events(stop_index=1)) + base_expectation = execute((expectation_event,), {expectation_event.event_index: historical}) + changed_expectation_result = execute((expectation_event,), {expectation_event.event_index: changed_expectation}) + results.append(_case_result("expected_blocker_metadata", runtime_snapshot_sha256=historical.snapshot_sha256, checks={ + "runtime_hash_unchanged": changed_expectation.snapshot_sha256 == historical.snapshot_sha256, + "metadata_hash_changed": changed_expectation.expected_metadata_sha256 != historical.expected_metadata_sha256, + "actual_input_unchanged": changed_expectation_result.input_sha256s == base_expectation.input_sha256s, + "actual_decision_unchanged": changed_expectation_result.decision_sha256s == base_expectation.decision_sha256s, + "actual_chain_unchanged": changed_expectation_result.chain_sha256s == base_expectation.chain_sha256s, + })) + + canonical = tuple(iter_shadow_campaign_events(stop_index=288)) + out_of_order = ( + *iter_shadow_campaign_events(start_index=192, stop_index=288), + *iter_shadow_campaign_events(start_index=0, stop_index=96), + *iter_shadow_campaign_events(start_index=96, stop_index=192), + ) + reordered = tuple(sorted(out_of_order, key=lambda item: item.event_index)) + canonical_result = execute(canonical) + reordered_result = execute(reordered) + results.append(_case_result("canonical_generation_order", canonical_event_count=len(canonical), checks={ + "generation_order_changed": tuple(item.event_index for item in out_of_order) != tuple(item.event_index for item in canonical), + "canonical_order_equal": reordered_result.event_sha256s == canonical_result.event_sha256s, + "actual_inputs_equal": reordered_result.input_sha256s == canonical_result.input_sha256s, + "actual_decisions_equal": reordered_result.decision_sha256s == canonical_result.decision_sha256s, + "actual_runtime_chains_equal": reordered_result.chain_sha256s == canonical_result.chain_sha256s, + })) + return tuple(results) + + +def _parallel(*functions: Callable[[], object]) -> tuple[object, ...]: + with ThreadPoolExecutor(max_workers=len(functions)) as executor: + futures = tuple(executor.submit(function) for function in functions) + return tuple(future.result() for future in futures) + + +def _run_concurrency_matrix(repository_sha: str) -> tuple[dict[str, object], ...]: + _, _, runtime_type, _ = _runtime_api() + spec, timeline = _bounded_inputs(events_per_segment=3) + expected_count = spec.event_count + reference = runtime_type( + MemoryShadowRepository(), repository_sha, + spec=spec, account_timeline=timeline, + ).run() + results: list[dict[str, object]] = [] + + repository = MemoryShadowRepository() + left = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + right = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + outcomes = _parallel(left.run, right.run) + final = left.inspect() + results.append(_case_result("two_identical_campaign_starts", checks={ + "both_complete": all(item.complete for item in outcomes), + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + "final_hash_deterministic": len({item.campaign_result_sha256 for item in outcomes}) == 1, + "final_hash_matches_reference": all( + item.campaign_result_sha256 == reference.campaign_result_sha256 + for item in outcomes + ), + "inspect_complete": final.complete, + })) + + repository = MemoryShadowRepository() + seed = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + seed.run(stop_index=2) + run_side = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + resume_side = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + outcomes = _parallel(run_side.run, resume_side.resume) + results.append(_case_result("run_and_resume", checks={ + "both_complete": all(item.complete for item in outcomes), + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + "final_hash_deterministic": len({item.campaign_result_sha256 for item in outcomes}) == 1, + "final_hash_matches_reference": all( + item.campaign_result_sha256 == reference.campaign_result_sha256 + for item in outcomes + ), + })) + + repository = MemoryShadowRepository() + runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline).run(stop_index=2) + first = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + second = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + outcomes = _parallel(first.resume, second.resume) + results.append(_case_result("two_resumes", checks={ + "both_complete": all(item.complete for item in outcomes), + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + "final_hash_deterministic": len({item.campaign_result_sha256 for item in outcomes}) == 1, + "final_hash_matches_reference": all( + item.campaign_result_sha256 == reference.campaign_result_sha256 + for item in outcomes + ), + })) + + repository = MemoryShadowRepository() + all_partition_events = tuple(iter_shadow_campaign_events(spec)) + partition_events = ( + all_partition_events[::2], + all_partition_events[1::2], + ) + partition_accounts = tuple( + tuple( + synthetic_account_snapshot_for_event(event.event_index, timeline) + for event in events + ) + for events in partition_events + ) + partition_start = Barrier(2) + + def persist_partition(index: int): + partition_start.wait() + return persist_actual_runtime_partition( + repository, + partition_events[index], + partition_accounts[index], + repository_sha=repository_sha, + ) + + outcomes = _parallel( + lambda: persist_partition(0), + lambda: persist_partition(1), + ) + recovery = runtime_type( + repository, repository_sha, spec=spec, account_timeline=timeline + ) + final = recovery.verify() + results.append(_case_result("different_event_partitions", checks={ + "partitions_are_disjoint": not ( + set(outcomes[0]["event_indices"]) & set(outcomes[1]["event_indices"]) + ), + "partitions_cover_exact_campaign": sorted( + (*outcomes[0]["event_indices"], *outcomes[1]["event_indices"]) + ) == list(range(expected_count)), + "each_partition_persisted_exactly_once": all( + item["committed_count"] == item["event_count"] + and item["replay_count"] == 0 for item in outcomes + ), + "partition_authority_zero": all( + item["zero_authority"] is True for item in outcomes + ), + "final_complete": final.complete, + "final_hash_deterministic": ( + final.campaign_result_sha256 == reference.campaign_result_sha256 + ), + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + })) + + repository = MemoryShadowRepository() + altered_segment = replace( + spec.segments[0], + generator_parameters={ + **dict(spec.segments[0].generator_parameters), + "noise_ppm": 17, + }, + ) + altered_spec = replace(spec, segments=(altered_segment, *spec.segments[1:])) + from .shadow_campaign_runtime import ( + ShadowCampaignOperationFailure, + ShadowCampaignSpecConflict, + ) + + left = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + right = runtime_type(repository, repository_sha, spec=altered_spec, account_timeline=timeline) + altered_reference = runtime_type( + MemoryShadowRepository(), repository_sha, + spec=altered_spec, account_timeline=timeline, + ).run() + candidate_hashes = { + spec.spec_sha256: reference.campaign_result_sha256, + altered_spec.spec_sha256: altered_reference.campaign_result_sha256, + } + start_barrier = Barrier(2) + + def atomic_start(runtime): + # Both contenders reach the operation boundary before either acquires + # the repository's per-campaign authority lock. + start_barrier.wait() + try: + return ("complete", runtime.run()) + except ShadowCampaignSpecConflict: + return ("conflict", None) + + outcomes = _parallel(lambda: atomic_start(left), lambda: atomic_start(right)) + statuses = tuple(item[0] for item in outcomes) + observed_specs = repository.observed_campaign_spec_hashes(spec.campaign_id) + winner = next((item[1] for item in outcomes if item[0] == "complete"), None) + winning_spec = next(iter(observed_specs)) if len(observed_specs) == 1 else None + results.append(_case_result("same_campaign_id_different_spec", checks={ + "spec_hash_changed": altered_spec.spec_sha256 != spec.spec_sha256, + "synchronized_before_campaign_authority": start_barrier.n_waiting == 0, + "exactly_one_complete": statuses.count("complete") == 1, + "explicit_conflict": statuses.count("conflict") == 1, + "single_spec_authority": len(observed_specs) == 1, + "winning_spec_is_one_contender": observed_specs <= {spec.spec_sha256, altered_spec.spec_sha256}, + "winning_hash_matches_winning_spec": ( + winner is not None + and winning_spec is not None + and winner.campaign_result_sha256 == candidate_hashes[winning_spec] + ), + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + "no_overwrite": ( + len(observed_specs) == 1 + and repository.row_counts()["audit.run_manifests"] == expected_count + ), + })) + + other_spec = replace(spec, campaign_id=f"{spec.campaign_id}-independent") + repository = MemoryShadowRepository() + left = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + right = runtime_type(repository, repository_sha, spec=other_spec, account_timeline=timeline) + outcomes = _parallel(left.run, right.run) + results.append(_case_result("different_campaign_ids_same_spec_shape", checks={ + "both_complete": all(item.complete for item in outcomes), + "lineage_isolated": outcomes[0].campaign_result_sha256 != outcomes[1].campaign_result_sha256, + "authority_sets_disjoint": repository.row_counts()["audit.run_manifests"] == 2 * expected_count, + })) + + repository = MemoryShadowRepository() + writer = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + reader = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + reader_start = Barrier(2) + outcomes = _parallel( + lambda: (reader_start.wait(), writer.run())[1], + lambda: (reader_start.wait(), reader.inspect())[1], + ) + final = reader.verify() + results.append(_case_result("inspect_reader_with_writer", checks={ + "writer_complete": outcomes[0].complete, + "reader_scanned_exact_campaign": outcomes[1].scanned_event_count == expected_count, + "reader_public_progress": ( + outcomes[1].invalid_bundle_count == 0 + and 0 <= outcomes[1].valid_bundle_count <= expected_count + ), + "final_complete": final.complete, + "final_hash_deterministic": final.campaign_result_sha256 == reference.campaign_result_sha256, + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + })) + + repository = MemoryShadowRepository() + writer = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + verifier = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + verifier_start = Barrier(2) + outcomes = _parallel( + lambda: (verifier_start.wait(), writer.run())[1], + lambda: (verifier_start.wait(), verifier.verify())[1], + ) + final = verifier.verify() + results.append(_case_result("verifier_with_writer", checks={ + "writer_complete": outcomes[0].complete, + "concurrent_verifier_executed": outcomes[1].scanned_event_count == expected_count, + "concurrent_verifier_canonical": outcomes[1].invalid_bundle_count == 0, + "final_verifier_complete": final.complete, + "final_hash_deterministic": final.campaign_result_sha256 == reference.campaign_result_sha256, + "one_authority_per_event": repository.row_counts()["audit.run_manifests"] == expected_count, + })) + + repository = MemoryShadowRepository() + runtime = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + runtime.run(stop_index=1) + first_run_id = _run_ids(spec, timeline, repository_sha)[0] + with repository.store.lock: + broken = deepcopy(repository.store.bundles[first_run_id]) + broken["bundle_hash"] = "0" * 64 + repository.store.bundles[first_run_id] = broken + corrupted_bytes = deepcopy(broken) + scan_failure = None + writer_failure = None + writer_spec = replace(spec, campaign_id=f"{spec.campaign_id}-writer") + writer = runtime_type(repository, repository_sha, spec=writer_spec, account_timeline=timeline) + corrupted_start = Barrier(2) + + def exact_corruption_progress(exc): + progress = exc.progress + return ( + exc.failure_stage == "repository_validation" + and progress["processed_event_count"] == 0 + and progress["committed_row_count"] == 0 + and progress["replay_count"] == 0 + and progress["first_missing_index"] == 0 + and progress["complete"] is False + and progress["production_write_count"] == 0 + and progress["production_submit_reachable"] is False + and progress["production_cancel_reachable"] is False + ) + + def corrupted_scan(): + nonlocal scan_failure + corrupted_start.wait() + try: + runtime.verify() + except ShadowCampaignOperationFailure as exc: + scan_failure = exc + + def rejected_writer(): + nonlocal writer_failure + corrupted_start.wait() + try: + writer.run() + except ShadowCampaignOperationFailure as exc: + writer_failure = exc + + _parallel(corrupted_scan, rejected_writer) + results.append(_case_result("corrupted_scan_with_new_writer", checks={ + "corruption_rejected": scan_failure is not None, + "new_writer_rejected": writer_failure is not None, + "corruption_public_progress_exact": ( + scan_failure is not None and exact_corruption_progress(scan_failure) + ), + "writer_public_progress_exact": ( + writer_failure is not None and exact_corruption_progress(writer_failure) + ), + "no_new_authority": repository.row_counts()["audit.run_manifests"] == 1, + "no_overwrite": repository.store.bundles[first_run_id] == corrupted_bytes, + })) + + repository = MemoryShadowRepository() + crash_runtime = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + response_lost = False + try: + crash_runtime.run( + stop_index=1, + crash_at="after_transaction_commit_before_response", + crash_event_index=0, + ) + except Exception as exc: + response_lost = getattr(exc, "crash_point", None) == "after_transaction_commit_before_response" + committed_before_retries = repository.row_counts()["audit.run_manifests"] + first_retry = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + second_retry = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + outcomes = _parallel( + lambda: first_retry.run(stop_index=1), + lambda: second_retry.run(stop_index=1), + ) + results.append(_case_result("post_commit_response_loss_with_retry", checks={ + "response_loss_observed": response_lost, + "commit_precedes_retries": committed_before_retries == 1, + "both_retries_replayed": all(item.replay_count == 1 and item.committed_event_count == 0 for item in outcomes), + "both_retries_completed_prefix": all(item.completed_event_count == 1 for item in outcomes), + "single_authoritative_row": repository.row_counts()["audit.run_manifests"] == 1, + "deterministic_retry_hash": len({item.final_decision_chain_sha256 for item in outcomes}) == 1, + })) + + return tuple(results) + + +def _run_crash_matrices(repository_sha: str): + campaign_points, campaign_crash_type, runtime_type, _ = _runtime_api() + if tuple(RUNTIME_CRASH_POINTS) != _EXPECTED_RUNTIME_CRASH_POINTS: + raise AssertionError("runtime crash point catalog/order changed") + if tuple(campaign_points) != _EXPECTED_CAMPAIGN_CRASH_POINTS: + raise AssertionError("campaign crash point catalog/order changed") + spec, timeline = _bounded_inputs(events_per_segment=2) + reference_repo = MemoryShadowRepository() + reference = runtime_type( + reference_repo, repository_sha, spec=spec, account_timeline=timeline + ).run() + run_ids = _run_ids(spec, timeline, repository_sha) + + runtime_results: list[dict[str, object]] = [] + for point_index, point in enumerate(RUNTIME_CRASH_POINTS): + repository = MemoryShadowRepository() + runtime = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + crashed = False + try: + runtime.run(crash_at=point, crash_event_index=0) + except Exception as exc: + crashed = getattr(exc, "crash_point", None) == point + committed_after_crash = repository.row_counts()["audit.run_manifests"] + before = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline).inspect() + post_commit = point == "after_transaction_commit_before_response" + expected_rows = 1 if post_commit else 0 + target_present = repository.load_bundle(run_ids[0]) is not None + recovered = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline).resume() + runtime_results.append(_case_result( + f"runtime_{point}", + crash_point=point, + lifecycle_ordinal=point_index, + committed_after_crash=committed_after_crash, + checks={ + "injected_crash_observed": crashed, + "exact_atomic_commitment": committed_after_crash == expected_rows, + "target_run_presence_exact": target_present is post_commit, + "inspect_valid_exact": before.valid_bundle_count == expected_rows, + "inspect_prefix_exact": before.completed_prefix_count == expected_rows, + "inspect_missing_exact": before.missing_event_count == spec.event_count - expected_rows, + "inspect_first_missing_exact": before.first_missing_index == expected_rows, + "resume_complete": recovered.complete, + "final_hash_matches": recovered.campaign_result_sha256 == reference.campaign_result_sha256, + "exact_final_authority": repository.row_counts()["audit.run_manifests"] == spec.event_count, + "no_production_write": recovered.safety_facts.production_write_count == 0, + }, + )) + + campaign_expectations = { + "before_corpus_initialization": (None, 0, 0, 0, 0, None), + "after_campaign_spec_validation": (None, 0, 0, 0, 0, None), + "before_first_event": (None, 0, 0, 0, 0, None), + "after_event_bundle_commit": (0, 0, 1, 0, 1, None), + "before_checkpoint_calculation": (0, 0, 1, 0, 1, None), + "after_checkpoint_calculation": (0, 1, 1, 0, 1, None), + "before_segment_completion": (1, 2, 2, 0, 2, None), + "after_segment_completion": (1, 2, 2, 0, 2, None), + "before_final_campaign_hash": (3, 4, 4, 0, None, None), + "after_final_hash_before_cli_response": ( + 3, 4, 4, 0, None, reference.campaign_result_sha256, + ), + } + expected_progress_keys = ( + "campaign_id", + "campaign_spec_sha256", + "processed_event_count", + "committed_row_count", + "replay_count", + "first_missing_index", + "campaign_result_sha256", + "network_read_count", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + "production_submit_reachable", + "production_cancel_reachable", + "real_account_data_used", + "operator_database_accessed", + "complete", + ) + campaign_results: list[dict[str, object]] = [] + for point_index, point in enumerate(campaign_points): + crash_index, expected_processed, expected_committed, expected_replay, expected_first_missing, expected_result = campaign_expectations[point] + repository = MemoryShadowRepository() + runtime = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline) + crashed = False + progress = None + try: + runtime.run(crash_at=point, crash_event_index=crash_index) + except campaign_crash_type as exc: + crashed = exc.crash_point == point + progress = dict(exc.progress) + row_count = repository.row_counts()["audit.run_manifests"] + canonical_rows = all(repository.load_bundle(run_id) is not None for run_id in run_ids[:row_count]) + recovered = runtime_type(repository, repository_sha, spec=spec, account_timeline=timeline).resume() + public_zero_keys = ( + "network_read_count", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + "production_submit_reachable", + "production_cancel_reachable", + "real_account_data_used", + "operator_database_accessed", + ) + campaign_results.append(_case_result( + f"campaign_{point}", + crash_point=point, + lifecycle_ordinal=point_index, + failure_progress_sha256=sha256_payload(progress), + committed_after_crash=row_count, + checks={ + "injected_crash_observed": crashed, + "exact_progress_schema": isinstance(progress, dict) and tuple(progress) == expected_progress_keys, + "exact_processed_count": progress is not None and progress["processed_event_count"] == expected_processed, + "exact_committed_count": progress is not None and progress["committed_row_count"] == expected_committed and row_count == expected_committed, + "exact_replay_count": progress is not None and progress["replay_count"] == expected_replay, + "exact_first_missing": progress is not None and progress["first_missing_index"] == expected_first_missing, + "exact_failure_result_hash": progress is not None and progress["campaign_result_sha256"] == expected_result, + "no_false_complete": progress is not None and progress["complete"] is False, + "public_safe_zero_authority": progress is not None and not any(progress[key] for key in public_zero_keys), + "committed_rows_canonical": canonical_rows, + "resume_complete": recovered.complete, + "final_hash_matches": recovered.campaign_result_sha256 == reference.campaign_result_sha256, + "exact_final_authority": repository.row_counts()["audit.run_manifests"] == spec.event_count, + "no_production_write": recovered.safety_facts.production_write_count == 0, + }, + )) + return tuple(runtime_results), tuple(campaign_results) + + +def _run_corruption_rejection(repository_sha: str) -> int: + from .shadow_campaign_runtime import ShadowCampaignOperationFailure + + _, _, runtime_type, _ = _runtime_api() + spec, timeline, repository, _, run_ids = _bounded_baseline(repository_sha) + with repository.store.lock: + corrupted = deepcopy(repository.store.bundles[run_ids[0]]) + corrupted["decision"]["input_hash"] = "0" * 64 + repository.store.bundles[run_ids[0]] = corrupted + expected_corrupted = deepcopy(corrupted) + row_count = repository.row_counts()["audit.run_manifests"] + try: + runtime_type( + repository, repository_sha, spec=spec, account_timeline=timeline + ).inspect() + except ShadowCampaignOperationFailure as exc: + expected_progress = { + "campaign_id": spec.campaign_id, + "campaign_spec_sha256": spec.spec_sha256, + "processed_event_count": 0, + "committed_row_count": 0, + "replay_count": 0, + "first_missing_index": 0, + "campaign_result_sha256": None, + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + "complete": False, + } + if ( + type(exc) is not ShadowCampaignOperationFailure + or exc.failure_stage != "repository_validation" + or dict(exc.progress) != expected_progress + ): + raise AssertionError("corruption rejection was not exact and structured") from exc + else: + raise AssertionError("corrupted committed campaign bundle was accepted") + with repository.store.lock: + no_repair = repository.store.bundles[run_ids[0]] == expected_corrupted + if ( + not no_repair + or repository.row_counts()["audit.run_manifests"] != row_count + ): + raise AssertionError("corruption rejection repaired or rewrote authority") + return 1 + + +def _full_baseline_and_replay(repository_sha: str): + _, _, runtime_type, _ = _runtime_api() + repository = MemoryShadowRepository() + runtime = runtime_type(repository, repository_sha) + baseline = runtime.run() + row_count = repository.row_counts()["audit.run_manifests"] + if not baseline.complete or baseline.completed_event_count != 25_920 or row_count != 25_920: + raise AssertionError("full historical campaign baseline did not complete") + replay = runtime_type(repository, repository_sha).replay() + row_count_after = repository.row_counts()["audit.run_manifests"] + replay_case = _case_result("exact_full_replay", event_count=25_920, campaign_result_sha256=replay.campaign_result_sha256, checks={ + "replay_complete": replay.complete, + "all_events_replayed": replay.replay_count == 25_920, + "no_new_commits": replay.committed_event_count == 0, + "row_count_unchanged": row_count_after == row_count, + "decision_chain_equal": replay.final_decision_chain_sha256 == baseline.final_decision_chain_sha256, + "segment_hashes_equal": replay.segment_result_hashes == baseline.segment_result_hashes, + "window_hashes_equal": replay.window_result_hashes == baseline.window_result_hashes, + "segment_counts_equal": replay.segment_decision_counts == baseline.segment_decision_counts, + "campaign_hash_equal": replay.campaign_result_sha256 == baseline.campaign_result_sha256, + }) + return baseline, replay, (replay_case,) + + +def _case_hashes(values: Sequence[Mapping[str, object]]) -> tuple[str, ...]: + return tuple(str(value["result_sha256"]) for value in values) + + +def validate_historical_shadow_campaign_verifier_result( + verifier: Mapping[str, object], + *, + repository_sha: str | None = None, +) -> None: + """Reject incomplete, reordered, caller-fed, or cross-repository results.""" + + if type(verifier) is not dict: + raise TypeError("campaign verifier result must be a mapping") + _validate_exact_verifier_tree(verifier) + if tuple(verifier) != VERIFIER_RESULT_KEYS: + raise ValueError("campaign verifier result keys/order differ from schema") + if repository_sha is not None and type(repository_sha) is not str: + raise TypeError("expected repository SHA must be exact text or None") + expected_repository_sha = None if repository_sha is None else validate_git_commit_sha(repository_sha) + if type(verifier["repository_sha"]) is not str: + raise TypeError("campaign verifier repository SHA must be exact text") + actual_repository_sha = validate_git_commit_sha(verifier["repository_sha"]) + if expected_repository_sha is not None and actual_repository_sha != expected_repository_sha: + raise ValueError("campaign verifier result is bound to another repository") + core = {key: verifier[key] for key in VERIFIER_RESULT_KEYS if key != "verifier_result_sha256"} + if ( + type(verifier["verifier_result_sha256"]) is not str + or _SHA256.fullmatch(verifier["verifier_result_sha256"]) is None + or verifier["verifier_result_sha256"] != sha256_payload(core) + ): + raise ValueError("campaign verifier result self-hash is invalid") + for key in ( + "schema_version", "equivalent_duration_days", "event_count", + "segment_count", "corrupted_bundle_rejections", + ): + if type(verifier[key]) is not int: + raise ValueError(f"{key} must be an exact integer, not bool/coercion") + + for key in ( + "verifier_implementation_sha256", + "expected_hash_manifest_sha256", + "corpus_spec_sha256", + "corpus_generator_sha256", + "corpus_generator_descriptor_sha256", + "generator_implementation_sha256", + "synthetic_account_timeline_sha256", + "event_sequence_sha256", + "corpus_campaign_result_sha256", + "final_decision_chain_sha256", + "campaign_result_sha256", + ): + if type(verifier[key]) is not str or _SHA256.fullmatch(verifier[key]) is None: + raise ValueError(f"{key} is not a canonical SHA-256 digest") + fixed = { + "schema_version": 1, + "operation": "phase8b_historical_shadow_campaign_verifier", + "status": "passed", + "campaign_version": SHADOW_CAMPAIGN_VERSION, + "verifier_version": CAMPAIGN_VERIFIER_VERSION, + "verifier_implementation_sha256": VERIFIER_IMPLEMENTATION_SHA256, + "expected_hash_manifest_version": EXPECTED_FULL_HASH_MANIFEST_VERSION, + "expected_hash_manifest_sha256": EXPECTED_FULL_HASH_MANIFEST_SHA256, + "corpus_generator_version": SHADOW_CAMPAIGN_GENERATOR_VERSION, + "corpus_spec_sha256": EXPECTED_FULL_CAMPAIGN_SPEC_SHA256, + "corpus_generator_sha256": EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + "corpus_generator_descriptor_sha256": EXPECTED_CORPUS_GENERATOR_SHA256, + "generator_implementation_sha256": EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + "synthetic_account_timeline_sha256": EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + "timeframe": "5m", + "equivalent_duration_days": 90, + "event_count": 25_920, + "segment_count": 12, + "event_sequence_sha256": EXPECTED_FULL_EVENT_SEQUENCE_SHA256, + "corpus_campaign_result_sha256": EXPECTED_FULL_CORPUS_RESULT_SHA256, + "corpus_segment_result_hashes": dict(EXPECTED_FULL_SEGMENT_HASHES), + "corpus_window_result_hashes": dict(EXPECTED_FULL_WINDOW_HASHES), + "corrupted_bundle_rejections": 1, + } + if any(verifier[key] != value for key, value in fixed.items()): + raise ValueError("campaign verifier fixed identity/hash field mismatch") + segment_ids = tuple(EXPECTED_FULL_SEGMENT_HASHES) + hash_catalogs = { + "corpus_segment_result_hashes": segment_ids, + "corpus_window_result_hashes": ("1d", "7d", "30d", "90d"), + "segment_result_hashes": segment_ids, + "window_result_hashes": ("1d", "7d", "30d", "90d"), + } + for field, expected_keys in hash_catalogs.items(): + values = verifier[field] + if ( + type(values) is not dict + or tuple(values) != expected_keys + or any( + type(value) is not str or _SHA256.fullmatch(value) is None + for value in values.values() + ) + ): + raise ValueError(f"{field} has an invalid key catalog or digest") + accepted = verifier["accepted_decision_count"] + blocked = verifier["blocked_decision_count"] + if ( + type(accepted) is not int + or accepted < 0 + or type(blocked) is not int + or blocked < 0 + or accepted + blocked != 25_920 + ): + raise ValueError("global campaign decision counts are invalid") + intent_count = verifier["shadow_intent_count"] + if type(intent_count) is not int or not accepted <= intent_count <= 25_920: + raise ValueError("campaign shadow intent count is invalid") + blocker_frequencies = verifier["blocker_frequencies"] + if ( + type(blocker_frequencies) is not dict + or tuple(blocker_frequencies) != tuple(sorted(blocker_frequencies)) + or any( + type(name) is not str + or name not in PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY + or type(count) is not int + or not 1 <= count <= blocked + for name, count in blocker_frequencies.items() + ) + or (blocked == 0) != (not blocker_frequencies) + or (blocked > 0 and sum(blocker_frequencies.values()) < blocked) + ): + raise ValueError("campaign blocker vocabulary/counts are invalid") + segment_counts = verifier["segment_decision_counts"] + if type(segment_counts) is not dict or tuple(segment_counts) != segment_ids: + raise ValueError("segment decision counts are incomplete or unordered") + segment_accepted = 0 + segment_blocked = 0 + for counts, segment_spec in zip( + segment_counts.values(), FULL_90D_CAMPAIGN_SPEC.segments + ): + if ( + type(counts) is not dict + or tuple(counts) != ("accepted", "blocked") + or type(counts["accepted"]) is not int + or type(counts["blocked"]) is not int + or counts["accepted"] < 0 + or counts["blocked"] < 0 + or counts["accepted"] + counts["blocked"] != segment_spec.event_count + ): + raise ValueError("segment decision count shape/total is invalid") + segment_accepted += counts["accepted"] + segment_blocked += counts["blocked"] + if (segment_accepted, segment_blocked) != (accepted, blocked): + raise ValueError("segment decision counts disagree with global totals") + for result_key, expected_cases in CASE_CATALOGS.items(): + if passed_campaign_case_count(verifier, result_key) != len(expected_cases): + raise ValueError(f"{result_key} pass count differs from exact catalog") + hash_key = result_key.replace("_results", "_result_hashes") + hashes = verifier[hash_key] + if ( + type(hashes) is not tuple + or len(hashes) != len(expected_cases) + or any( + type(value) is not str or _SHA256.fullmatch(value) is None + for value in hashes + ) + or hashes != tuple( + value["result_sha256"] for value in verifier[result_key] + ) + ): + raise ValueError(f"{hash_key} differs from exact executable cases") + if any( + value["final_campaign_sha256"] != verifier["campaign_result_sha256"] + for value in verifier["restart_results"] + if value["case"].startswith("restart_checkpoint_") + ): + raise ValueError("restart results differ from the full campaign result") + if ( + verifier["replay_results"][0]["campaign_result_sha256"] + != verifier["campaign_result_sha256"] + ): + raise ValueError("replay result differs from the full campaign result") + mutations = verifier["mutation_results"] + if ( + {value["parent_campaign_sha256"] for value in mutations} + != {verifier["campaign_result_sha256"]} + or len({value["parent_policy_chain_sha256"] for value in mutations}) != 1 + or len({value["mutated_policy_chain_sha256"] for value in mutations}) + != len(_MUTATION_CASES) + or len({value["mutation_lineage_sha256"] for value in mutations}) + != len(_MUTATION_CASES) + or any( + value["parent_policy_chain_sha256"] == value["mutated_policy_chain_sha256"] + for value in mutations + ) + ): + raise ValueError("mutation lineage is incomplete or non-distinct") + zero = verifier["zero_write_facts"] + expected_zero_keys = ( + "network_read_count", + "network_write_count", + "production_transport_call_count", + "authenticated_endpoint_call_count", + "credential_read_count", + "production_write_count", + "production_submit_reachable", + "production_cancel_reachable", + "real_account_data_used", + "operator_database_accessed", + ) + zero_count_keys = expected_zero_keys[:6] + zero_flag_keys = expected_zero_keys[6:] + if ( + type(zero) is not dict + or tuple(zero) != expected_zero_keys + or any(type(zero[key]) is not int or zero[key] != 0 for key in zero_count_keys) + or any(zero[key] is not False for key in zero_flag_keys) + ): + raise PermissionError("campaign verifier observed forbidden authority") + postgres = verifier["postgresql_campaign"] + if ( + type(postgres) is not dict + or tuple(postgres) + != ("classification", "event_count", "authoritative_storage_target") + or type(postgres["classification"]) is not str + or postgres["classification"] != POSTGRESQL_CAMPAIGN_NOT_EXECUTED + or type(postgres["event_count"]) is not int + or postgres["event_count"] != 0 + or type(postgres["authoritative_storage_target"]) is not str + or postgres["authoritative_storage_target"] != "PostgreSQL_16" + ): + raise ValueError("campaign verifier PostgreSQL classification is invalid") + + +def _run_historical_shadow_campaign_verifier_uncached( + repository_sha: str, +) -> dict[str, object]: + baseline, replay, replay_results = _full_baseline_and_replay(repository_sha) + full_corpus = calculate_shadow_campaign_corpus_hashes() + compact_corpus = calculate_shadow_campaign_corpus_hashes(event_limit=1_440) + validate_expected_hash_manifest() + generator_implementation_sha256 = validate_generator_source_implementation() + validate_expected_corpus_results( + campaign_spec_sha256=FULL_90D_CAMPAIGN_SPEC.spec_sha256, + synthetic_account_timeline_sha256=FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + corpus_generator_sha256=SHADOW_CAMPAIGN_GENERATOR_SHA256, + full_result=full_corpus, + compact_result=compact_corpus, + ) + if baseline.event_sequence_sha256 != EXPECTED_FULL_EVENT_SEQUENCE_SHA256: + raise AssertionError("runtime event sequence differs from expected manifest") + if replay.event_sequence_sha256 != baseline.event_sequence_sha256: + raise AssertionError("full replay event sequence differs from baseline") + if baseline.accepted_decision_count + baseline.blocked_decision_count != 25_920: + raise AssertionError("campaign decision counts do not cover all events") + + restart_results = _run_restart_matrix(repository_sha) + mutation_results = _run_mutation_matrix( + repository_sha, + parent_campaign_sha256=baseline.campaign_result_sha256, + ) + anti_lookahead_results = _run_anti_lookahead_matrix(repository_sha) + concurrency_results = _run_concurrency_matrix(repository_sha) + runtime_crash_results, campaign_crash_results = _run_crash_matrices(repository_sha) + corrupted_bundle_rejections = _run_corruption_rejection(repository_sha) + + zero_write_facts = { + "network_read_count": baseline.safety_facts.network_read_count, + "network_write_count": baseline.safety_facts.network_write_count, + "production_transport_call_count": baseline.safety_facts.production_transport_call_count, + "authenticated_endpoint_call_count": baseline.safety_facts.authenticated_endpoint_call_count, + "credential_read_count": baseline.safety_facts.credential_read_count, + "production_write_count": baseline.safety_facts.production_write_count, + "production_submit_reachable": baseline.safety_facts.production_submit_reachable, + "production_cancel_reachable": baseline.safety_facts.production_cancel_reachable, + "real_account_data_used": baseline.safety_facts.real_account_data_used, + "operator_database_accessed": baseline.safety_facts.operator_database_accessed, + } + if any(zero_write_facts.values()): + raise PermissionError("historical campaign verifier observed forbidden authority") + + segment_decision_counts = { + segment_id: {"accepted": counts["accepted"], "blocked": counts["blocked"]} + for segment_id, counts in baseline.segment_decision_counts.items() + } + core: dict[str, object] = { + "schema_version": 1, + "operation": "phase8b_historical_shadow_campaign_verifier", + "status": "passed", + "repository_sha": repository_sha, + "campaign_version": SHADOW_CAMPAIGN_VERSION, + "verifier_version": CAMPAIGN_VERIFIER_VERSION, + "verifier_implementation_sha256": VERIFIER_IMPLEMENTATION_SHA256, + "expected_hash_manifest_version": EXPECTED_FULL_HASH_MANIFEST_VERSION, + "expected_hash_manifest_sha256": EXPECTED_FULL_HASH_MANIFEST_SHA256, + "corpus_generator_version": SHADOW_CAMPAIGN_GENERATOR_VERSION, + "corpus_spec_sha256": FULL_90D_CAMPAIGN_SPEC.spec_sha256, + "corpus_generator_sha256": generator_implementation_sha256, + "corpus_generator_descriptor_sha256": SHADOW_CAMPAIGN_GENERATOR_SHA256, + "generator_implementation_sha256": generator_implementation_sha256, + "synthetic_account_timeline_sha256": FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + "timeframe": FULL_90D_CAMPAIGN_SPEC.timeframe, + "equivalent_duration_days": FULL_90D_CAMPAIGN_SPEC.equivalent_duration_days, + "event_count": baseline.completed_event_count, + "segment_count": FULL_90D_CAMPAIGN_SPEC.segment_count, + "event_sequence_sha256": baseline.event_sequence_sha256, + "corpus_campaign_result_sha256": full_corpus.campaign_result_sha256, + "corpus_segment_result_hashes": dict(full_corpus.segment_hashes), + "corpus_window_result_hashes": dict(full_corpus.window_hashes), + "segment_result_hashes": dict(baseline.segment_hashes), + "window_result_hashes": dict(baseline.window_hashes), + "accepted_decision_count": baseline.accepted_decision_count, + "blocked_decision_count": baseline.blocked_decision_count, + "segment_decision_counts": segment_decision_counts, + "shadow_intent_count": baseline.shadow_intent_count, + "blocker_frequencies": dict(sorted(baseline.blocker_frequencies.items())), + "final_decision_chain_sha256": baseline.final_decision_chain_sha256, + "campaign_result_sha256": baseline.campaign_result_sha256, + "restart_results": restart_results, + "restart_result_hashes": _case_hashes(restart_results), + "replay_results": replay_results, + "replay_result_hashes": _case_hashes(replay_results), + "mutation_results": mutation_results, + "mutation_result_hashes": _case_hashes(mutation_results), + "anti_lookahead_results": anti_lookahead_results, + "anti_lookahead_result_hashes": _case_hashes(anti_lookahead_results), + "concurrency_results": concurrency_results, + "concurrency_result_hashes": _case_hashes(concurrency_results), + "runtime_crash_results": runtime_crash_results, + "runtime_crash_result_hashes": _case_hashes(runtime_crash_results), + "campaign_crash_results": campaign_crash_results, + "campaign_crash_result_hashes": _case_hashes(campaign_crash_results), + "corrupted_bundle_rejections": corrupted_bundle_rejections, + "zero_write_facts": zero_write_facts, + "postgresql_campaign": { + "classification": POSTGRESQL_CAMPAIGN_NOT_EXECUTED, + "event_count": 0, + "authoritative_storage_target": "PostgreSQL_16", + }, + } + result = {**core, "verifier_result_sha256": sha256_payload(core)} + validate_historical_shadow_campaign_verifier_result(result, repository_sha=repository_sha) + return result + + +def run_historical_shadow_campaign_verifier( + repository_sha: str, +) -> dict[str, object]: + """Execute the exact campaign and every verifier matrix on each call.""" + + if type(repository_sha) is not str: + raise TypeError("campaign verifier repository SHA must be exact text") + repository_sha = validate_git_commit_sha(repository_sha) + result = _run_historical_shadow_campaign_verifier_uncached(repository_sha) + return deepcopy(result) + +__all__ = [ + "CAMPAIGN_VERIFIER_VERSION", + "CASE_CATALOGS", + "CASE_RESULT_SCHEMAS", + "POSTGRESQL_CAMPAIGN_NOT_EXECUTED", + "PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY", + "VERIFIER_IMPLEMENTATION_SHA256", + "VERIFIER_RESULT_KEYS", + "passed_campaign_case_count", + "run_historical_shadow_campaign_verifier", + "validate_historical_shadow_campaign_verifier_result", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier_runtime.py b/open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier_runtime.py new file mode 100644 index 0000000..5d977ef --- /dev/null +++ b/open-core/src/secure_eval_wrapper/live/shadow_campaign_verifier_runtime.py @@ -0,0 +1,633 @@ +"""Actual shared-runtime observations used by the campaign verifier. + +This module deliberately contains no expected outcomes. It executes exact generated +5-minute inputs through :class:`ShadowAssuranceRuntime`, reloads each canonical bundle +from the repository, and derives an ordered chain from persisted runtime facts. +""" + +from __future__ import annotations + +from contextlib import ExitStack +from dataclasses import dataclass +from itertools import zip_longest +from types import MappingProxyType +from typing import Iterable, Mapping, Sequence + +from secure_eval_wrapper.data_collection.hashing import sha256_payload + +from .identity import RuntimeRepositoryIdentity, validate_git_commit_sha +from .shadow_campaign_models import ( + ShadowCampaignEvent, + SyntheticAccountTimelineSnapshot, +) +from .shadow_campaign_runtime import ( + campaign_policy_chain_seed, + campaign_policy_chain_sha256, + campaign_policy_hash_projection, + campaign_scenario_for_event, + derive_shadow_campaign_run_id, +) +from .shadow_repository import MemoryShadowRepository, ShadowPersistenceConflict +from .shadow_runtime import FixtureShadowMarketSource, ShadowAssuranceRuntime + +def _exact_repository_sha(value: str) -> str: + if type(value) is not str: + raise TypeError("verifier runtime repository SHA must be exact text") + return validate_git_commit_sha(value) + + +@dataclass(frozen=True, slots=True) +class ActualRuntimeSequence: + """Canonical facts observed from one ordered shared-runtime traversal.""" + + event_sha256s: tuple[str, ...] + account_sha256s: tuple[str, ...] + input_sha256s: tuple[str, ...] + decision_sha256s: tuple[str, ...] + summary_sha256s: tuple[str, ...] + bundle_sha256s: tuple[str, ...] + chain_sha256s: tuple[str, ...] + accepted: tuple[bool, ...] + blockers: tuple[tuple[str, ...], ...] + row_count: int + zero_authority_facts: Mapping[str, object] + + def __post_init__(self) -> None: + lengths = { + len(self.event_sha256s), + len(self.account_sha256s), + len(self.input_sha256s), + len(self.decision_sha256s), + len(self.summary_sha256s), + len(self.bundle_sha256s), + len(self.chain_sha256s), + len(self.accepted), + len(self.blockers), + } + if lengths != {self.row_count}: + raise ValueError("actual runtime sequence fields have inconsistent lengths") + object.__setattr__( + self, + "zero_authority_facts", + MappingProxyType(dict(self.zero_authority_facts)), + ) + + @property + def final_chain_sha256(self) -> str: + if not self.chain_sha256s: + raise ValueError("empty runtime sequence has no final chain") + return self.chain_sha256s[-1] + + @property + def policy_input_sha256s(self) -> tuple[str, ...]: + return self.input_sha256s + + @property + def policy_decision_sha256s(self) -> tuple[str, ...]: + return self.decision_sha256s + + @property + def policy_summary_sha256s(self) -> tuple[str, ...]: + return self.summary_sha256s + + @property + def policy_chain_sha256s(self) -> tuple[str, ...]: + return self.chain_sha256s + + @property + def final_policy_chain_sha256(self) -> str: + return self.final_chain_sha256 + + +def execute_actual_runtime_sequence( + events: Sequence[ShadowCampaignEvent], + accounts: Sequence[SyntheticAccountTimelineSnapshot], + *, + repository_sha: str, +) -> ActualRuntimeSequence: + """Execute ordered generated inputs and observe canonical persisted decisions.""" + + repository_sha = _exact_repository_sha(repository_sha) + if isinstance(events, (str, bytes, bytearray)) or not isinstance(events, Sequence): + raise TypeError("actual runtime events must be a sequence") + if isinstance(accounts, (str, bytes, bytearray)) or not isinstance( + accounts, Sequence + ): + raise TypeError("actual runtime accounts must be a sequence") + if not events or len(events) != len(accounts): + raise ValueError("actual runtime events/accounts must be non-empty and aligned") + if any(type(event) is not ShadowCampaignEvent for event in events): + raise TypeError("actual runtime sequence contains an unsupported event") + if any( + type(account) is not SyntheticAccountTimelineSnapshot for account in accounts + ): + raise TypeError("actual runtime sequence contains an unsupported account") + if any(event.timeframe != "5m" for event in events): + raise ValueError("actual runtime verifier requires exact 5m events") + if any( + later.event_index <= earlier.event_index + for earlier, later in zip(events, events[1:]) + ): + raise ValueError("actual runtime verifier events must be strictly ordered") + campaign_ids = {event.campaign_id for event in events} + campaign_spec_hashes = {event.campaign_spec_sha256 for event in events} + if len(campaign_ids) != 1 or len(campaign_spec_hashes) != 1: + raise ValueError( + "actual runtime verifier sequence must use one campaign authority" + ) + + repository = MemoryShadowRepository() + identity = RuntimeRepositoryIdentity(repository_sha, "git_checkout") + runtime = ShadowAssuranceRuntime( + repository=repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: identity, + ) + event_hashes: list[str] = [] + account_hashes: list[str] = [] + input_hashes: list[str] = [] + decision_hashes: list[str] = [] + summary_hashes: list[str] = [] + bundle_hashes: list[str] = [] + chain_hashes: list[str] = [] + accepted: list[bool] = [] + blockers: list[tuple[str, ...]] = [] + chain = campaign_policy_chain_seed() + campaign_id = next(iter(campaign_ids)) + with repository.campaign_authority(campaign_id): + for event, account in zip(events, accounts): + scenario = campaign_scenario_for_event(event, account) + run_id = derive_shadow_campaign_run_id( + campaign_id=event.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=repository_sha, + campaign_spec_sha256=event.campaign_spec_sha256, + ) + observed = runtime.run_generated_fixture_event( + scenario, + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=run_id, + ) + bundle = repository.load_bundle(run_id) + if bundle is None: + raise AssertionError( + "actual runtime verifier observed no persisted bundle" + ) + decision = bundle["decision"] + summary = bundle["summary"] + if ( + observed.input_hash != decision["input_hash"] + or observed.decision_hash != decision["decision_hash"] + or observed.accepted != decision["accepted"] + or tuple(observed.blockers) != tuple(decision["blockers"]) + or observed.shadow_intent_count + != summary["shadow_intent_count"] + ): + raise AssertionError( + "runtime response differs from its canonical bundle" + ) + projection = campaign_policy_hash_projection(event, account, bundle) + event_hashes.append(event.event_sha256) + account_hashes.append(account.snapshot_sha256) + input_hashes.append(str(projection["policy_input_sha256"])) + decision_hashes.append(str(projection["policy_decision_sha256"])) + summary_hashes.append(str(projection["policy_summary_sha256"])) + bundle_hashes.append(str(bundle["bundle_hash"])) + accepted.append(projection["accepted"] is True) + event_blockers = tuple(str(value) for value in projection["blockers"]) + blockers.append(event_blockers) + chain = campaign_policy_chain_sha256( + prior_chain_sha256=chain, + event=event, + account=account, + bundle=bundle, + ) + chain_hashes.append(chain) + + row_count = repository.row_counts()["audit.run_manifests"] + facts = { + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + } + for run_id in tuple(repository.store.bundles): + bundle = repository.load_bundle(run_id) + if bundle is None: + raise AssertionError("actual runtime bundle disappeared during verification") + summary = bundle["summary"] + for key in facts: + observed_value = summary[key] + if observed_value not in (0, False): + raise PermissionError( + "actual runtime verifier observed forbidden authority" + ) + return ActualRuntimeSequence( + event_sha256s=tuple(event_hashes), + account_sha256s=tuple(account_hashes), + input_sha256s=tuple(input_hashes), + decision_sha256s=tuple(decision_hashes), + summary_sha256s=tuple(summary_hashes), + bundle_sha256s=tuple(bundle_hashes), + chain_sha256s=tuple(chain_hashes), + accepted=tuple(accepted), + blockers=tuple(blockers), + row_count=row_count, + zero_authority_facts=facts, + ) + + +def compare_actual_runtime_streams( + left_inputs: Iterable[ + tuple[ShadowCampaignEvent, SyntheticAccountTimelineSnapshot] + ], + right_inputs: Iterable[ + tuple[ShadowCampaignEvent, SyntheticAccountTimelineSnapshot] + ], + *, + repository_sha: str, +) -> Mapping[str, object]: + """Compare two real shared-runtime streams without retaining input objects.""" + + repository_sha = _exact_repository_sha(repository_sha) + if isinstance(left_inputs, (str, bytes, bytearray)) or isinstance( + right_inputs, (str, bytes, bytearray) + ): + raise TypeError("runtime comparison inputs must be event/account iterables") + left_iterator = iter(left_inputs) + right_iterator = iter(right_inputs) + try: + first_left = next(left_iterator) + first_right = next(right_iterator) + except StopIteration as exc: + raise ValueError("runtime comparison streams must both be non-empty") from exc + + def validate_pair(value: object): + if ( + not isinstance(value, tuple) + or len(value) != 2 + or type(value[0]) is not ShadowCampaignEvent + or type(value[1]) is not SyntheticAccountTimelineSnapshot + ): + raise TypeError("runtime comparison item has the wrong exact types") + event, account = value + if event.timeframe != "5m": + raise ValueError("runtime comparison requires exact 5m events") + return event, account + + first_left_event, _ = validate_pair(first_left) + first_right_event, _ = validate_pair(first_right) + left_campaign = first_left_event.campaign_id + right_campaign = first_right_event.campaign_id + left_spec = first_left_event.campaign_spec_sha256 + right_spec = first_right_event.campaign_spec_sha256 + left_repository = MemoryShadowRepository() + right_repository = MemoryShadowRepository() + identity = RuntimeRepositoryIdentity(repository_sha, "git_checkout") + left_runtime = ShadowAssuranceRuntime( + repository=left_repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: identity, + ) + right_runtime = ShadowAssuranceRuntime( + repository=right_repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: identity, + ) + chain_seed = campaign_policy_chain_seed() + left_chain = chain_seed + right_chain = chain_seed + observed_blockers = ({}, {}) + counts = [0, 0] + shared_count = 0 + divergence = { + "event": None, + "account": None, + "input": None, + "decision": None, + "chain": None, + } + sentinel = object() + + def execute_one(repository, runtime, item, campaign_id, spec_hash, chain): + event, account = validate_pair(item) + if ( + event.campaign_id != campaign_id + or event.campaign_spec_sha256 != spec_hash + ): + raise ValueError("runtime comparison stream changed campaign authority") + run_id = derive_shadow_campaign_run_id( + campaign_id=event.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=repository_sha, + campaign_spec_sha256=event.campaign_spec_sha256, + ) + observed = runtime.run_generated_fixture_event( + campaign_scenario_for_event(event, account), + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=run_id, + ) + bundle = repository.load_bundle(run_id) + if bundle is None: + raise AssertionError("runtime comparison produced no canonical row") + decision = bundle["decision"] + summary = bundle["summary"] + if ( + observed.input_hash != decision["input_hash"] + or observed.decision_hash != decision["decision_hash"] + or observed.accepted != decision["accepted"] + or tuple(observed.blockers) != tuple(decision["blockers"]) + ): + raise AssertionError("runtime comparison response differs from its row") + for key in ( + "network_read_count", "network_write_count", + "production_transport_call_count", "authenticated_endpoint_call_count", + "credential_read_count", "production_write_count", + "production_submit_reachable", "production_cancel_reachable", + ): + if summary[key] not in (0, False): + raise PermissionError("runtime comparison observed forbidden authority") + projection = campaign_policy_hash_projection(event, account, bundle) + next_chain = campaign_policy_chain_sha256( + prior_chain_sha256=chain, + event=event, + account=account, + bundle=bundle, + ) + return event, account, projection, next_chain + + with ExitStack() as stack: + stack.enter_context(left_repository.campaign_authority(left_campaign)) + stack.enter_context(right_repository.campaign_authority(right_campaign)) + # Chain the already-peeked items without materializing either suffix. + def restored(first, remainder): + yield first + yield from remainder + stream = zip_longest( + restored(first_left, left_iterator), + restored(first_right, right_iterator), + fillvalue=sentinel, + ) + for ordinal, (left_item, right_item) in enumerate(stream): + left_observed = right_observed = None + if left_item is not sentinel: + left_observed = execute_one( + left_repository, left_runtime, left_item, + left_campaign, left_spec, left_chain, + ) + left_chain = left_observed[3] + counts[0] += 1 + left_account = left_observed[1] + observed_blockers[0].setdefault( + left_account.runtime_payload_sha256, set() + ).update(left_observed[2]["blockers"]) + if right_item is not sentinel: + right_observed = execute_one( + right_repository, right_runtime, right_item, + right_campaign, right_spec, right_chain, + ) + right_chain = right_observed[3] + counts[1] += 1 + right_account = right_observed[1] + observed_blockers[1].setdefault( + right_account.runtime_payload_sha256, set() + ).update(right_observed[2]["blockers"]) + if left_observed is None or right_observed is None: + continue + shared_count += 1 + left_event, left_account, left_projection, left_observed_chain = left_observed + right_event, right_account, right_projection, right_observed_chain = right_observed + comparisons = { + "event": left_event.event_sha256 == right_event.event_sha256, + "account": left_account.runtime_payload_sha256 == right_account.runtime_payload_sha256, + "input": left_projection["policy_input_sha256"] == right_projection["policy_input_sha256"], + "decision": left_projection["policy_decision_sha256"] == right_projection["policy_decision_sha256"], + "chain": left_observed_chain == right_observed_chain, + } + for name, equal in comparisons.items(): + if not equal and divergence[name] is None: + divergence[name] = ordinal + if left_repository.row_counts()["audit.run_manifests"] != counts[0]: + raise AssertionError("left comparison authority count differs from stream") + if right_repository.row_counts()["audit.run_manifests"] != counts[1]: + raise AssertionError("right comparison authority count differs from stream") + return MappingProxyType({ + "left_event_count": counts[0], + "right_event_count": counts[1], + "shared_event_count": shared_count, + "first_event_divergence_index": divergence["event"], + "first_account_divergence_index": divergence["account"], + "first_input_divergence_index": divergence["input"], + "first_decision_divergence_index": divergence["decision"], + "first_chain_divergence_index": divergence["chain"], + "left_final_chain_sha256": left_chain, + "right_final_chain_sha256": right_chain, + "zero_authority": True, + "left_observed_blockers_by_account_runtime_sha256": MappingProxyType({ + key: tuple(sorted(value)) + for key, value in sorted(observed_blockers[0].items()) + }), + "right_observed_blockers_by_account_runtime_sha256": MappingProxyType({ + key: tuple(sorted(value)) + for key, value in sorted(observed_blockers[1].items()) + }), + }) + + +def persist_actual_runtime_partition( + repository: MemoryShadowRepository, + events: Sequence[ShadowCampaignEvent], + accounts: Sequence[SyntheticAccountTimelineSnapshot], + *, + repository_sha: str, +) -> Mapping[str, object]: + """Persist one real, possibly non-contiguous campaign event partition.""" + + repository_sha = _exact_repository_sha(repository_sha) + if type(repository) is not MemoryShadowRepository: + raise TypeError("partition verifier requires the exact memory repository") + if isinstance(events, (str, bytes, bytearray)) or not isinstance(events, Sequence): + raise TypeError("partition events must be a sequence") + if isinstance(accounts, (str, bytes, bytearray)) or not isinstance( + accounts, Sequence + ): + raise TypeError("partition accounts must be a sequence") + if not events or len(events) != len(accounts): + raise ValueError("partition events/accounts must be non-empty and aligned") + if any(type(event) is not ShadowCampaignEvent for event in events): + raise TypeError("partition contains an unsupported event") + if any( + type(account) is not SyntheticAccountTimelineSnapshot + for account in accounts + ): + raise TypeError("partition contains an unsupported account") + if any(event.timeframe != "5m" for event in events): + raise ValueError("partition verifier requires exact 5m events") + if any( + later.event_index <= earlier.event_index + for earlier, later in zip(events, events[1:]) + ): + raise ValueError("partition events must be strictly ordered") + campaign_ids = {event.campaign_id for event in events} + campaign_spec_hashes = {event.campaign_spec_sha256 for event in events} + if len(campaign_ids) != 1 or len(campaign_spec_hashes) != 1: + raise ValueError("partition must use one campaign authority") + + identity = RuntimeRepositoryIdentity(repository_sha, "git_checkout") + runtime = ShadowAssuranceRuntime( + repository=repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: identity, + ) + committed = 0 + replayed = 0 + campaign_id = next(iter(campaign_ids)) + with repository.campaign_authority(campaign_id): + for event, account in zip(events, accounts): + run_id = derive_shadow_campaign_run_id( + campaign_id=event.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=repository_sha, + campaign_spec_sha256=event.campaign_spec_sha256, + ) + observed = runtime.run_generated_fixture_event( + campaign_scenario_for_event(event, account), + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=run_id, + ) + bundle = repository.load_bundle(run_id) + if bundle is None: + raise AssertionError("partition event produced no canonical bundle") + decision = bundle["decision"] + summary = bundle["summary"] + if ( + observed.input_hash != decision["input_hash"] + or observed.decision_hash != decision["decision_hash"] + or observed.accepted != decision["accepted"] + or tuple(observed.blockers) != tuple(decision["blockers"]) + or observed.shadow_intent_count != summary["shadow_intent_count"] + ): + raise AssertionError("partition response differs from canonical row") + replayed += int(observed.replayed) + committed += int(not observed.replayed) + for key in ( + "network_read_count", "network_write_count", + "production_transport_call_count", "authenticated_endpoint_call_count", + "credential_read_count", "production_write_count", + "production_submit_reachable", "production_cancel_reachable", + ): + if summary[key] not in (0, False): + raise PermissionError("partition observed forbidden authority") + return MappingProxyType({ + "event_indices": tuple(event.event_index for event in events), + "event_count": len(events), + "committed_count": committed, + "replay_count": replayed, + "campaign_spec_sha256": next(iter(campaign_spec_hashes)), + "zero_authority": True, + }) + + +def execute_actual_same_run_id_conflict( + base_event: ShadowCampaignEvent, + base_account: SyntheticAccountTimelineSnapshot, + mutated_event: ShadowCampaignEvent, + mutated_account: SyntheticAccountTimelineSnapshot, + *, + repository_sha: str, +) -> Mapping[str, object]: + """Prove that a forced logical-run collision cannot overwrite authority.""" + + repository_sha = _exact_repository_sha(repository_sha) + for event in (base_event, mutated_event): + if type(event) is not ShadowCampaignEvent: + raise TypeError("same-run conflict event must use the exact campaign type") + for account in (base_account, mutated_account): + if type(account) is not SyntheticAccountTimelineSnapshot: + raise TypeError("same-run conflict account must use the exact timeline type") + if ( + base_event.campaign_id != mutated_event.campaign_id + or base_event.campaign_spec_sha256 + != mutated_event.campaign_spec_sha256 + or base_event.event_index != mutated_event.event_index + ): + raise ValueError("same-run conflict inputs must describe one logical event") + if ( + base_event.event_sha256 == mutated_event.event_sha256 + and base_account.snapshot_sha256 == mutated_account.snapshot_sha256 + ): + raise ValueError("same-run conflict requires a changed runtime input") + + repository = MemoryShadowRepository() + identity = RuntimeRepositoryIdentity(repository_sha, "git_checkout") + runtime = ShadowAssuranceRuntime( + repository=repository, + market_source=FixtureShadowMarketSource(), + identity_resolver=lambda: identity, + ) + base_run_id = derive_shadow_campaign_run_id( + campaign_id=base_event.campaign_id, + event_index=base_event.event_index, + event_sha256=base_event.event_sha256, + synthetic_account_snapshot_sha256=base_account.snapshot_sha256, + repository_sha=repository_sha, + campaign_spec_sha256=base_event.campaign_spec_sha256, + ) + with repository.campaign_authority(base_event.campaign_id): + runtime.run_generated_fixture_event( + campaign_scenario_for_event(base_event, base_account), + campaign_event_hash=base_event.event_sha256, + timeframe=base_event.timeframe, + shadow_run_id=base_run_id, + ) + original = repository.load_bundle(base_run_id) + if original is None: + raise AssertionError("same-run conflict baseline did not persist") + conflict = False + try: + runtime.run_generated_fixture_event( + campaign_scenario_for_event(mutated_event, mutated_account), + campaign_event_hash=mutated_event.event_sha256, + timeframe=mutated_event.timeframe, + shadow_run_id=base_run_id, + ) + except ShadowPersistenceConflict: + conflict = True + after = repository.load_bundle(base_run_id) + if ( + not conflict + or after is None + or repository.row_counts()["audit.run_manifests"] != 1 + or after["bundle_hash"] != original["bundle_hash"] + ): + raise AssertionError("same-run mutation did not fail atomically") + return MappingProxyType( + { + "explicit_conflict": True, + "single_authoritative_row": True, + "original_bundle_preserved": True, + "original_bundle_sha256": original["bundle_hash"], + } + ) + + +__all__ = [ + "ActualRuntimeSequence", + "compare_actual_runtime_streams", + "execute_actual_runtime_sequence", + "execute_actual_same_run_id_conflict", + "persist_actual_runtime_partition", +] diff --git a/open-core/src/secure_eval_wrapper/live/shadow_repository.py b/open-core/src/secure_eval_wrapper/live/shadow_repository.py index dded7fe..3aedb77 100644 --- a/open-core/src/secure_eval_wrapper/live/shadow_repository.py +++ b/open-core/src/secure_eval_wrapper/live/shadow_repository.py @@ -3,11 +3,12 @@ import json import re +from contextlib import contextmanager from dataclasses import dataclass, field from datetime import date, datetime from decimal import Decimal from enum import Enum -from threading import RLock +from threading import RLock, local from typing import Mapping from uuid import UUID @@ -23,10 +24,13 @@ validate_shadow_bundle_payload, validate_shadow_manifest_row, ) +from .shadow_campaign_models import campaign_event_id from .shadow_models import SHADOW_RUNTIME_VERSION, ShadowDecisionRecord SHADOW_DATABASE_PREFIX = "secure_eval_phase8b_shadow_" +SHADOW_CAMPAIGN_DATABASE_PREFIX = "secure_eval_phase8b_shadow_campaign_" +SHADOW_CAMPAIGN_SCENARIO_PREFIX = "phase8b-shadow-campaign:" LOCAL_SHADOW_POSTGRES_HOSTS = frozenset({"127.0.0.1", "::1"}) PERSISTENCE_CRASH_POINTS = frozenset({ "after_decision_persist_before_summary", @@ -39,6 +43,18 @@ class ShadowPersistenceConflict(RuntimeError): pass +class ShadowCampaignPersistenceConflict(ShadowPersistenceConflict): + """One campaign ID cannot acquire two specification authorities.""" + + +class ShadowCampaignRepositoryCorruption(ShadowCampaignPersistenceConflict): + """A committed row failed canonical validation at a public-safe index.""" + + def __init__(self, first_invalid_event_index: int | None) -> None: + self.first_invalid_event_index = first_invalid_event_index + super().__init__("campaign repository contains an invalid committed row") + + class ShadowInjectedCrash(RuntimeError): def __init__(self, crash_point: str) -> None: self.crash_point = crash_point @@ -60,6 +76,168 @@ def validate_shadow_database_name(value: str) -> str: return value +def validate_shadow_campaign_database_name(value: str) -> str: + if type(value) is not str or re.fullmatch( + r"secure_eval_phase8b_shadow_campaign_[a-z0-9][a-z0-9_]{0,20}", + value, + ) is None: + raise PermissionError( + "historical shadow campaign persistence requires an explicit disposable " + "secure_eval_phase8b_shadow_campaign_ database" + ) + validate_shadow_database_name(value) + return value + + +def validate_shadow_campaign_id(value: str) -> str: + if type(value) is not str or re.fullmatch( + r"[a-z0-9][a-z0-9_-]{0,95}", value + ) is None: + raise ValueError("shadow campaign ID must use safe lowercase text") + return value + + +def shadow_campaign_scenario_prefix(campaign_id: str) -> str: + return ( + f"{SHADOW_CAMPAIGN_SCENARIO_PREFIX}" + f"{validate_shadow_campaign_id(campaign_id)}:spec:" + ) + + +def shadow_campaign_spec_from_scenario_id( + scenario_id: object, campaign_id: str +) -> str | None: + identity = shadow_campaign_event_identity_from_scenario_id(scenario_id) + if identity is None or identity[0] != validate_shadow_campaign_id(campaign_id): + return None + return identity[1] + + +def shadow_campaign_event_index_from_id(event_id: object) -> int: + """Validate and decode one canonical lineage-independent event ID.""" + + if type(event_id) is not str: + raise ShadowBundleValidationError("campaign event ID must be text") + match = re.fullmatch( + r"shadow-campaign-event-([0-9]+)-([0-9a-f]{16})", event_id + ) + if match is None: + raise ShadowBundleValidationError("campaign event ID is malformed") + event_index = int(match.group(1)) + if event_id != campaign_event_id(event_index=event_index): + raise ShadowBundleValidationError( + "campaign event ID disagrees with its deterministic index authority" + ) + return event_index + + +def shadow_campaign_identity_from_scenario_id( + scenario_id: object, +) -> tuple[str, str] | None: + """Return an exact campaign/spec identity for campaign-owned scenarios.""" + + if type(scenario_id) is not str or not scenario_id.startswith( + SHADOW_CAMPAIGN_SCENARIO_PREFIX + ): + return None + campaign_id, separator, remainder = scenario_id[ + len(SHADOW_CAMPAIGN_SCENARIO_PREFIX) : + ].partition(":spec:") + if separator != ":spec:": + raise ShadowBundleValidationError("campaign scenario ID is malformed") + validate_shadow_campaign_id(campaign_id) + spec_sha256, event_separator, event_suffix = remainder.partition(":event:") + if ( + event_separator != ":event:" + or not event_suffix + or len(spec_sha256) != 64 + or any(character not in "0123456789abcdef" for character in spec_sha256) + ): + raise ShadowBundleValidationError( + "campaign scenario ID has malformed specification authority" + ) + parts = event_suffix.split(":") + if ( + len(parts) != 4 + or re.fullmatch( + r"shadow-campaign-event-[0-9]+-[0-9a-f]{16}", parts[0] + ) + is None + or len(parts[1]) != 64 + or any(character not in "0123456789abcdef" for character in parts[1]) + or parts[2] != "account" + or len(parts[3]) != 64 + or any(character not in "0123456789abcdef" for character in parts[3]) + ): + raise ShadowBundleValidationError( + "campaign scenario ID has malformed logical-event authority" + ) + shadow_campaign_event_index_from_id(parts[0]) + return campaign_id, spec_sha256 + + + +def shadow_campaign_event_identity_from_scenario_id( + scenario_id: object, +) -> tuple[str, str, str] | None: + """Return the incremental campaign/spec/logical-event authority.""" + + identity = shadow_campaign_identity_from_scenario_id(scenario_id) + if identity is None: + return None + event_id = str(scenario_id).split(":event:", 1)[1].split(":", 1)[0] + return identity[0], identity[1], event_id + + +def _campaign_event_index_from_untrusted_payload(payload: object) -> int | None: + """Best-effort public-safe index extraction without trusting row contents.""" + + try: + if not isinstance(payload, Mapping): + return None + decision = payload.get("decision") + if not isinstance(decision, Mapping): + return None + identity = shadow_campaign_event_identity_from_scenario_id( + decision.get("scenario_id") + ) + if identity is None: + return None + return shadow_campaign_event_index_from_id(identity[2]) + except (ShadowBundleValidationError, TypeError, ValueError): + return None + + +@dataclass(slots=True) +class _CampaignAuthorityState: + campaign_id: str + spec_hashes: set[str] = field(default_factory=set) + logical_events: dict[ + tuple[str, str], tuple[str, str] + ] = field(default_factory=dict) + + +def _record_campaign_bundle_authority( + state: _CampaignAuthorityState, + bundle: Mapping[str, object], +) -> None: + decision = bundle["decision"] + identity = shadow_campaign_event_identity_from_scenario_id( + decision["scenario_id"] + ) + if identity is None or identity[0] != state.campaign_id: + return + _, spec_sha256, event_id = identity + logical_key = (spec_sha256, event_id) + authority = (str(decision["shadow_run_id"]), str(decision["scenario_id"])) + existing = state.logical_events.get(logical_key) + if existing is not None and existing != authority: + raise ShadowCampaignPersistenceConflict( + "campaign logical event already has a different authoritative bundle" + ) + state.spec_hashes.add(spec_sha256) + state.logical_events[logical_key] = authority + def validate_shadow_postgres_host(value: str) -> str: if type(value) is not str or value not in LOCAL_SHADOW_POSTGRES_HOSTS: raise PermissionError( @@ -169,7 +347,9 @@ def _preparing_payload(decision: ShadowDecisionRecord) -> dict[str, object]: @dataclass class ShadowMemoryStore: bundles: dict[UUID, dict[str, object]] = field(default_factory=dict) + campaign_spec_bindings: dict[str, str] = field(default_factory=dict) lock: RLock = field(default_factory=RLock) + campaign_locks: dict[str, RLock] = field(default_factory=dict) class MemoryShadowRepository: @@ -179,7 +359,86 @@ class MemoryShadowRepository: def __init__(self, store: ShadowMemoryStore | None = None) -> None: self.store = ShadowMemoryStore() if store is None else store + self._campaign_authority_local = local() + def _active_campaign_authority( + self, campaign_id: str + ) -> _CampaignAuthorityState | None: + states = getattr(self._campaign_authority_local, "states", None) + return None if states is None else states.get(campaign_id) + + def _scan_campaign_authorities_locked( + self, campaign_id: str + ) -> _CampaignAuthorityState: + state = _CampaignAuthorityState(campaign_id) + for run_id, payload in self.store.bundles.items(): + try: + bundle = validate_shadow_bundle_payload(payload) + if str(run_id) != str(bundle["decision"]["shadow_run_id"]): + raise ShadowBundleValidationError( + "memory campaign row key disagrees with bundle authority" + ) + _record_campaign_bundle_authority(state, bundle) + except ( + ShadowBundleValidationError, + ShadowCampaignPersistenceConflict, + ) as exc: + raise ShadowCampaignRepositoryCorruption( + _campaign_event_index_from_untrusted_payload(payload) + ) from exc + return state + + def _synchronize_campaign_spec_cache( + self, campaign_id: str, state: _CampaignAuthorityState + ) -> None: + if len(state.spec_hashes) == 1: + self.store.campaign_spec_bindings[campaign_id] = next( + iter(state.spec_hashes) + ) + else: + self.store.campaign_spec_bindings.pop(campaign_id, None) + + @contextmanager + def campaign_authority(self, campaign_id: str): + """Serialize one campaign and cache its incremental logical authorities.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + states = getattr(self._campaign_authority_local, "states", None) + if states is None: + states = {} + self._campaign_authority_local.states = states + if campaign_id in states: + yield + return + with self.store.lock: + campaign_lock = self.store.campaign_locks.setdefault( + campaign_id, RLock() + ) + with campaign_lock: + with self.store.lock: + state = self._scan_campaign_authorities_locked(campaign_id) + self._synchronize_campaign_spec_cache(campaign_id, state) + states[campaign_id] = state + try: + yield + finally: + states.pop(campaign_id, None) + + def refresh_campaign_authority( + self, campaign_id: str + ) -> frozenset[str]: + """Rescan all rows while the campaign guard is held.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + if self._active_campaign_authority(campaign_id) is None: + raise ShadowCampaignPersistenceConflict( + "campaign authority refresh requires the campaign guard" + ) + with self.store.lock: + state = self._scan_campaign_authorities_locked(campaign_id) + self._synchronize_campaign_spec_cache(campaign_id, state) + self._campaign_authority_local.states[campaign_id] = state + return frozenset(state.spec_hashes) def persist_bundle( self, decision: ShadowDecisionRecord, @@ -189,6 +448,9 @@ def persist_bundle( if crash_at is not None and crash_at not in PERSISTENCE_CRASH_POINTS: raise ValueError("unknown persistence crash point") final = shadow_bundle_payload(decision) + campaign_identity = shadow_campaign_event_identity_from_scenario_id( + final["decision"]["scenario_id"] + ) with self.store.lock: existing = self.store.bundles.get(decision.shadow_run_id) if existing is not None: @@ -198,14 +460,42 @@ def persist_bundle( "same shadow run ID has a different authoritative payload" ) return True - staged = dict(self.store.bundles) - staged[decision.shadow_run_id] = _preparing_payload(decision) - if crash_at == "after_decision_persist_before_summary": - raise ShadowInjectedCrash(crash_at) - staged[decision.shadow_run_id] = final - if crash_at == "before_transaction_commit": + state: _CampaignAuthorityState | None = None + if campaign_identity is not None: + campaign_id, campaign_spec_sha256, event_id = campaign_identity + state = self._active_campaign_authority(campaign_id) + if state is None: + raise ShadowCampaignPersistenceConflict( + "new campaign bundles require the campaign authority guard" + ) + if state.spec_hashes - {campaign_spec_sha256}: + raise ShadowCampaignPersistenceConflict( + "campaign ID is already bound to a different specification" + ) + logical_key = (campaign_spec_sha256, event_id) + authority = ( + str(decision.shadow_run_id), + str(final["decision"]["scenario_id"]), + ) + observed = state.logical_events.get(logical_key) + if observed is not None and observed != authority: + raise ShadowCampaignPersistenceConflict( + "campaign logical event already has a different " + "authoritative bundle" + ) + if crash_at in { + "after_decision_persist_before_summary", + "before_transaction_commit", + }: + # The in-memory double models transactional rollback: neither + # pre-commit crash point can expose a partial or final row. raise ShadowInjectedCrash(crash_at) - self.store.bundles = staged + self.store.bundles[decision.shadow_run_id] = final + if state is not None: + _record_campaign_bundle_authority(state, final) + self._synchronize_campaign_spec_cache( + state.campaign_id, state + ) if crash_at == "after_transaction_commit_before_response": raise ShadowPostCommitCrash(crash_at) return False @@ -215,6 +505,82 @@ def load_bundle(self, shadow_run_id: UUID) -> dict[str, object] | None: payload = self.store.bundles.get(shadow_run_id) return None if payload is None else validate_shadow_bundle_payload(payload) + def iter_expected_bundles( + self, + shadow_run_ids: tuple[UUID, ...], + *, + batch_size: int = 256, + ): + if type(shadow_run_ids) is not tuple or any( + type(run_id) is not UUID for run_id in shadow_run_ids + ): + raise TypeError("expected shadow run IDs must be an exact UUID tuple") + if ( + isinstance(batch_size, bool) + or not isinstance(batch_size, int) + or not 1 <= batch_size <= 1024 + ): + raise ValueError("shadow read batch_size must be between 1 and 1024") + for start in range(0, len(shadow_run_ids), batch_size): + run_ids = shadow_run_ids[start : start + batch_size] + with self.store.lock: + payloads = tuple(self.store.bundles.get(run_id) for run_id in run_ids) + for payload in payloads: + yield ( + None + if payload is None + else validate_shadow_bundle_payload(payload) + ) + + def observed_campaign_spec_hashes( + self, campaign_id: str, *, batch_size: int = 256 + ) -> frozenset[str]: + """Return spec authorities from the guarded global bundle scan.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + if ( + isinstance(batch_size, bool) + or not isinstance(batch_size, int) + or not 1 <= batch_size <= 1024 + ): + raise ValueError("shadow read batch_size must be between 1 and 1024") + state = self._active_campaign_authority(campaign_id) + if state is not None: + return frozenset(state.spec_hashes) + with self.campaign_authority(campaign_id): + state = self._active_campaign_authority(campaign_id) + if state is None: + raise AssertionError("campaign guard did not install authority state") + return frozenset(state.spec_hashes) + + def iter_campaign_event_authorities( + self, campaign_id: str, campaign_spec_sha256: str + ): + """Stream guarded logical-event authorities for one campaign spec.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + state = self._active_campaign_authority(campaign_id) + if state is None: + raise ShadowCampaignPersistenceConflict( + "campaign event authority iteration requires the campaign guard" + ) + for (spec_sha256, event_id), authority in state.logical_events.items(): + if spec_sha256 == campaign_spec_sha256: + yield event_id, authority[0], authority[1] + + def campaign_event_authority( + self, campaign_id: str, campaign_spec_sha256: str, event_id: str + ) -> tuple[str, str] | None: + """Return one guarded logical-event authority in constant time.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + state = self._active_campaign_authority(campaign_id) + if state is None: + raise ShadowCampaignPersistenceConflict( + "campaign event authority lookup requires the campaign guard" + ) + return state.logical_events.get((campaign_spec_sha256, event_id)) + def row_counts(self) -> Mapping[str, int]: with self.store.lock: return {"audit.run_manifests": len(self.store.bundles)} @@ -235,6 +601,9 @@ def __init__( self.expected_host = validate_shadow_postgres_host(expected_host) self.connection = connection self.expected_database = validate_shadow_database_name(expected_database) + self._campaign_authority_state: _CampaignAuthorityState | None = None + self._campaign_authority_depth = 0 + self._campaign_operation_lock = RLock() self._verify_target() _ROW_COLUMNS = ( @@ -313,12 +682,40 @@ def _verify_target(self) -> None: raise PermissionError( "shadow database contains non-shadow audit manifest rows" ) - cursor.execute( - f"SELECT {self._ROW_SELECT} FROM audit.run_manifests " - "WHERE storage_ref='phase8b_shadow_assurance' ORDER BY run_id" - ) - for manifest_row in cursor.fetchall(): - validate_shadow_manifest_row(self._manifest_row(manifest_row)) + # Validate existing JSON authorities in bounded keyset pages. A + # fresh-process resume may open a database with 25,920+ bundles; + # target verification must not materialize them all at once. + last_run_id: UUID | None = None + while True: + if last_run_id is None: + cursor.execute( + f"SELECT {self._ROW_SELECT} FROM audit.run_manifests " + "WHERE storage_ref='phase8b_shadow_assurance' " + "ORDER BY run_id LIMIT 256" + ) + else: + cursor.execute( + f"SELECT {self._ROW_SELECT} FROM audit.run_manifests " + "WHERE storage_ref='phase8b_shadow_assurance' " + "AND run_id>%s ORDER BY run_id LIMIT 256", + (last_run_id,), + ) + manifest_rows = tuple(cursor.fetchall()) + for manifest_row in manifest_rows: + raw_row = self._manifest_row(manifest_row) + try: + validate_shadow_manifest_row(raw_row) + except ShadowBundleValidationError as exc: + raise ShadowCampaignRepositoryCorruption( + _campaign_event_index_from_untrusted_payload( + raw_row.get("manifest_jsonb") + ) + ) from exc + if len(manifest_rows) < 256: + break + last_run_id = UUID( + str(self._value(manifest_rows[-1], "run_id", 0)) + ) cursor.execute( "SELECT table_schema,table_name FROM information_schema.tables " "WHERE table_type='BASE TABLE' " @@ -346,6 +743,127 @@ def _verify_target(self) -> None: finally: self.connection.rollback() + def _scan_campaign_authorities( + self, campaign_id: str, *, batch_size: int = 256 + ) -> _CampaignAuthorityState: + state = _CampaignAuthorityState(campaign_id) + last_run_id: UUID | None = None + while True: + rows = () + try: + with self.connection.cursor() as cursor: + if last_run_id is None: + cursor.execute( + f"SELECT {self._ROW_SELECT} FROM audit.run_manifests " + "WHERE storage_ref='phase8b_shadow_assurance' " + "ORDER BY run_id LIMIT %s", + (batch_size,), + ) + else: + cursor.execute( + f"SELECT {self._ROW_SELECT} FROM audit.run_manifests " + "WHERE storage_ref='phase8b_shadow_assurance' " + "AND run_id>%s ORDER BY run_id LIMIT %s", + (last_run_id, batch_size), + ) + rows = tuple(cursor.fetchall()) + finally: + self.connection.rollback() + if not rows: + break + for row in rows: + raw_row = self._manifest_row(row) + try: + bundle = validate_shadow_manifest_row(raw_row) + _record_campaign_bundle_authority(state, bundle) + except ( + ShadowBundleValidationError, + ShadowCampaignPersistenceConflict, + ) as exc: + raise ShadowCampaignRepositoryCorruption( + _campaign_event_index_from_untrusted_payload( + raw_row.get("manifest_jsonb") + ) + ) from exc + last_run_id = UUID(str(self._manifest_row(rows[-1])["run_id"])) + return state + + @contextmanager + def campaign_authority(self, campaign_id: str): + """Serialize one connection operation before taking its session lock.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + with self._campaign_operation_lock: + with self._campaign_authority_session(campaign_id): + yield + + @contextmanager + def _campaign_authority_session(self, campaign_id: str): + """Hold one session lock and cache logical-event bindings.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + active = self._campaign_authority_state + if active is not None: + if active.campaign_id != campaign_id: + raise ShadowCampaignPersistenceConflict( + "one PostgreSQL connection cannot guard two campaigns" + ) + self._campaign_authority_depth += 1 + try: + yield + finally: + self._campaign_authority_depth -= 1 + return + acquired = False + try: + with self.connection.cursor() as cursor: + cursor.execute( + "SELECT pg_advisory_lock(hashtextextended(%s,0))", + (campaign_id,), + ) + cursor.fetchone() + self.connection.rollback() + acquired = True + self._campaign_authority_state = self._scan_campaign_authorities( + campaign_id + ) + self._campaign_authority_depth = 1 + yield + finally: + self._campaign_authority_state = None + self._campaign_authority_depth = 0 + if acquired: + try: + with self.connection.cursor() as cursor: + cursor.execute( + "SELECT pg_advisory_unlock(hashtextextended(%s,0))", + (campaign_id,), + ) + row = cursor.fetchone() + if not bool(self._value(row, "pg_advisory_unlock", 0)): + raise ShadowCampaignPersistenceConflict( + "campaign advisory lock authority was lost" + ) + finally: + self.connection.rollback() + + def refresh_campaign_authority( + self, campaign_id: str, *, batch_size: int = 256 + ) -> frozenset[str]: + """Rescan all rows while the session campaign guard is held.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + active = self._campaign_authority_state + if active is None or active.campaign_id != campaign_id: + raise ShadowCampaignPersistenceConflict( + "campaign authority refresh requires the campaign guard" + ) + active = self._scan_campaign_authorities( + campaign_id, batch_size=batch_size + ) + self._campaign_authority_state = active + return frozenset(active.spec_hashes) + def persist_bundle( self, decision: ShadowDecisionRecord, @@ -356,6 +874,44 @@ def persist_bundle( raise ValueError("unknown persistence crash point") final = shadow_bundle_payload(decision) validate_shadow_bundle_payload(final) + campaign_identity = shadow_campaign_event_identity_from_scenario_id( + final["decision"]["scenario_id"] + ) + state: _CampaignAuthorityState | None = None + if campaign_identity is not None: + campaign_id, campaign_spec_sha256, event_id = campaign_identity + active = self._campaign_authority_state + if active is None: + existing = self.load_bundle(decision.shadow_run_id) + if existing is None: + raise ShadowCampaignPersistenceConflict( + "new campaign bundles require the campaign authority guard" + ) + if existing.get("bundle_hash") != final["bundle_hash"]: + raise ShadowPersistenceConflict( + "same shadow run ID has a different authoritative payload" + ) + return True + if active.campaign_id != campaign_id: + raise ShadowCampaignPersistenceConflict( + "active campaign guard has a different identity" + ) + if active.spec_hashes - {campaign_spec_sha256}: + raise ShadowCampaignPersistenceConflict( + "campaign ID is already bound to a different specification" + ) + logical_key = (campaign_spec_sha256, event_id) + authority = ( + str(decision.shadow_run_id), + str(final["decision"]["scenario_id"]), + ) + observed = active.logical_events.get(logical_key) + if observed is not None and observed != authority: + raise ShadowCampaignPersistenceConflict( + "campaign logical event already has a different " + "authoritative bundle" + ) + state = active preparing = _preparing_payload(decision) inserted = False with self.connection.transaction(): @@ -370,10 +926,20 @@ def persist_bundle( decision.shadow_run_id, decision.input_hash, decision.configuration_hash, - sha256_payload({"repository_commit_sha": decision.repository_commit_sha}), + sha256_payload( + { + "repository_commit_sha": ( + decision.repository_commit_sha + ) + } + ), final["bundle_hash"], "phase8b_shadow_assurance", - json.dumps(preparing, sort_keys=True, separators=(",", ":")), + json.dumps( + preparing, + sort_keys=True, + separators=(",", ":"), + ), datetime.fromisoformat("2026-07-18T00:00:00+00:00"), ), ) @@ -390,6 +956,8 @@ def persist_bundle( raise ShadowPersistenceConflict( "same shadow run ID has a different authoritative payload" ) + if state is not None: + _record_campaign_bundle_authority(state, final) return True if crash_at == "after_decision_persist_before_summary": raise ShadowInjectedCrash(crash_at) @@ -403,9 +971,13 @@ def persist_bundle( ), ) if cursor.rowcount != 1: - raise ShadowPersistenceConflict("shadow bundle finalization lost authority") + raise ShadowPersistenceConflict( + "shadow bundle finalization lost authority" + ) if crash_at == "before_transaction_commit": raise ShadowInjectedCrash(crash_at) + if state is not None: + _record_campaign_bundle_authority(state, final) if inserted and crash_at == "after_transaction_commit_before_response": raise ShadowPostCommitCrash(crash_at) return False @@ -423,6 +995,101 @@ def load_bundle(self, shadow_run_id: UUID) -> dict[str, object] | None: return None return validate_shadow_manifest_row(self._manifest_row(row)) + def iter_expected_bundles( + self, + shadow_run_ids: tuple[UUID, ...], + *, + batch_size: int = 256, + ): + if type(shadow_run_ids) is not tuple or any( + type(run_id) is not UUID for run_id in shadow_run_ids + ): + raise TypeError("expected shadow run IDs must be an exact UUID tuple") + if ( + isinstance(batch_size, bool) + or not isinstance(batch_size, int) + or not 1 <= batch_size <= 1024 + ): + raise ValueError("shadow read batch_size must be between 1 and 1024") + for start in range(0, len(shadow_run_ids), batch_size): + run_ids = shadow_run_ids[start : start + batch_size] + rows = () + try: + with self.connection.cursor() as cursor: + cursor.execute( + f"SELECT {self._ROW_SELECT} FROM audit.run_manifests " + "WHERE storage_ref='phase8b_shadow_assurance' " + "AND run_id = ANY(%s::uuid[])", + (list(run_ids),), + ) + rows = tuple(cursor.fetchall()) + finally: + self.connection.rollback() + by_run_id: dict[UUID, dict[str, object]] = {} + for row in rows: + manifest_row = self._manifest_row(row) + run_id = UUID(str(manifest_row["run_id"])) + if run_id in by_run_id: + raise ShadowBundleValidationError( + "campaign repository returned duplicate run IDs" + ) + by_run_id[run_id] = validate_shadow_manifest_row(manifest_row) + for run_id in run_ids: + yield by_run_id.get(run_id) + + def observed_campaign_spec_hashes( + self, campaign_id: str, *, batch_size: int = 256 + ) -> frozenset[str]: + """Return spec authorities from the session-locked global scan.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + if ( + isinstance(batch_size, bool) + or not isinstance(batch_size, int) + or not 1 <= batch_size <= 1024 + ): + raise ValueError("shadow read batch_size must be between 1 and 1024") + active = self._campaign_authority_state + if active is not None: + if active.campaign_id != campaign_id: + raise ShadowCampaignPersistenceConflict( + "active campaign guard has a different identity" + ) + return frozenset(active.spec_hashes) + with self.campaign_authority(campaign_id): + active = self._campaign_authority_state + if active is None: + raise AssertionError("campaign guard did not install authority state") + return frozenset(active.spec_hashes) + + def iter_campaign_event_authorities( + self, campaign_id: str, campaign_spec_sha256: str + ): + """Stream session-guarded logical-event authorities for one spec.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + active = self._campaign_authority_state + if active is None or active.campaign_id != campaign_id: + raise ShadowCampaignPersistenceConflict( + "campaign event authority iteration requires the campaign guard" + ) + for (spec_sha256, event_id), authority in active.logical_events.items(): + if spec_sha256 == campaign_spec_sha256: + yield event_id, authority[0], authority[1] + + def campaign_event_authority( + self, campaign_id: str, campaign_spec_sha256: str, event_id: str + ) -> tuple[str, str] | None: + """Return one session-guarded logical-event authority in constant time.""" + + campaign_id = validate_shadow_campaign_id(campaign_id) + active = self._campaign_authority_state + if active is None or active.campaign_id != campaign_id: + raise ShadowCampaignPersistenceConflict( + "campaign event authority lookup requires the campaign guard" + ) + return active.logical_events.get((campaign_spec_sha256, event_id)) + def row_counts(self) -> Mapping[str, int]: with self.connection.cursor() as cursor: cursor.execute( @@ -441,16 +1108,27 @@ def row_counts(self) -> Mapping[str, int]: "MemoryShadowRepository", "PERSISTENCE_CRASH_POINTS", "PostgresShadowRepository", + "SHADOW_CAMPAIGN_DATABASE_PREFIX", + "SHADOW_CAMPAIGN_SCENARIO_PREFIX", "SHADOW_DATABASE_PREFIX", "ShadowBundleValidationError", + "ShadowCampaignPersistenceConflict", + "ShadowCampaignRepositoryCorruption", "ShadowInjectedCrash", "ShadowMemoryStore", "ShadowPersistenceConflict", "ShadowPostCommitCrash", "shadow_bundle_payload", + "shadow_campaign_event_index_from_id", + "shadow_campaign_event_identity_from_scenario_id", + "shadow_campaign_identity_from_scenario_id", + "shadow_campaign_scenario_prefix", + "shadow_campaign_spec_from_scenario_id", "shadow_decision_payload", "decode_and_validate_shadow_bundle", "validate_shadow_bundle_payload", + "validate_shadow_campaign_database_name", + "validate_shadow_campaign_id", "validate_shadow_database_name", "validate_shadow_manifest_row", "validate_shadow_postgres_host", diff --git a/open-core/src/secure_eval_wrapper/live/shadow_runtime.py b/open-core/src/secure_eval_wrapper/live/shadow_runtime.py index 4ed2b02..db7dffb 100644 --- a/open-core/src/secure_eval_wrapper/live/shadow_runtime.py +++ b/open-core/src/secure_eval_wrapper/live/shadow_runtime.py @@ -75,7 +75,7 @@ from .shadow_scenarios import SHADOW_FIXTURE_TIME, ShadowScenarioSpec, scenario_by_id -RUNTIME_CRASH_POINTS = frozenset({ +RUNTIME_CRASH_POINTS = ( "market_snapshot_normalized", "synthetic_account_validated", "risk_evaluated", @@ -85,7 +85,60 @@ "after_decision_persist_before_summary", "before_transaction_commit", "after_transaction_commit_before_response", -}) +) + + +def _freeze_generated_payload(value: object) -> object: + """Detach and recursively freeze generated fixture containers.""" + + if isinstance(value, Mapping): + frozen: dict[str, object] = {} + for key, item in value.items(): + if type(key) is not str or not key: + raise TypeError("generated shadow payload keys must be non-empty text") + frozen[key] = _freeze_generated_payload(item) + return MappingProxyType(frozen) + if isinstance(value, (list, tuple)): + return tuple(_freeze_generated_payload(item) for item in value) + return value + + +@dataclass(frozen=True, slots=True) +class GeneratedShadowScenarioSpec: + """Expectation-free generated-event input accepted only by the 5m path.""" + + scenario_id: str + category: str + account_payload: Mapping[str, object] + market_payload: Mapping[str, object] + request_payload: Mapping[str, object] + + def __post_init__(self) -> None: + if type(self.scenario_id) is not str or not self.scenario_id: + raise ValueError("generated shadow scenario ID must be non-empty") + if self.category not in {"account", "market"}: + raise ValueError("generated shadow scenario category must be account or market") + for name in ("account_payload", "market_payload", "request_payload"): + value = getattr(self, name) + if not isinstance(value, Mapping): + raise TypeError(f"generated shadow {name} must be a mapping") + object.__setattr__( + self, + name, + _freeze_generated_payload(value), + ) + + @property + def input_hash(self) -> str: + return sha256_payload( + { + "scenario_id": self.scenario_id, + "category": self.category, + "account": dict(self.account_payload), + "market": dict(self.market_payload), + "request": dict(self.request_payload), + } + ) class ShadowAuthorityError(PermissionError): @@ -834,7 +887,9 @@ def _account_validation_blockers( return ("synthetic_permission_not_trade_enabled",) balances = payload.get("balances") positions = payload.get("positions") - if not isinstance(balances, list) or not isinstance(positions, list): + if not isinstance(balances, (list, tuple)) or not isinstance( + positions, (list, tuple) + ): return ("malformed_account_snapshot",) try: balance_values = [ @@ -996,6 +1051,141 @@ def run_fixture( _public_provenance=None, ) + def run_generated_fixture_event( + self, + scenario: GeneratedShadowScenarioSpec, + *, + campaign_event_hash: str, + timeframe: str, + shadow_run_id: UUID, + crash_at: str | None = None, + ) -> ShadowRunSummary: + """Run one sealed generated-corpus event through the fixture authority.""" + + if type(self.market_source) is not FixtureShadowMarketSource: + raise ShadowAuthorityError( + "generated fixture execution requires the exact fixture source" + ) + if type(scenario) is not GeneratedShadowScenarioSpec: + raise ShadowAuthorityError( + "generated fixture input must use the exact expectation-free type" + ) + if type(shadow_run_id) is not UUID: + raise ShadowAuthorityError("generated fixture run ID must be an exact UUID") + if ( + type(campaign_event_hash) is not str + or len(campaign_event_hash) != 64 + or any( + character not in "0123456789abcdef" + for character in campaign_event_hash + ) + ): + raise ShadowAuthorityError( + "generated fixture campaign event hash must be lowercase SHA-256" + ) + if timeframe != "5m": + raise ShadowAuthorityError( + "generated fixture campaign execution requires exact 5m timeframe" + ) + if scenario.request_payload.get("campaign_event_hash") != campaign_event_hash: + raise ShadowAuthorityError( + "generated fixture scenario is not bound to the campaign event hash" + ) + if scenario.request_payload.get("timeframe") != timeframe: + raise ShadowAuthorityError( + "generated fixture scenario is not bound to the campaign timeframe" + ) + forbidden_oracle_tokens = frozenset( + { + "regime", + "regimelabel", + "segmentlabel", + "expectedresult", + "expectedblockers", + "expectedaccountblockers", + "expectedshadowintentcount", + } + ) + forbidden_authority_tokens = frozenset( + { + "transport", + "httpclient", + "authenticatedvenue", + "productionbroker", + "credentialresolver", + "operatordatabaseconnector", + "authenticatedproofrunner", + "operatorbootstrapexecutor", + "authenticatedendpointtransport", + "arbitraryendpointtransport", + "operatordatabaseconnection", + "fixtransport", + "productionfixtransport", + "leverage", + "derivatives", + "productionfix", + "submitorder", + "cancelorder", + "withdraw", + "transfer", + "borrow", + "setleverage", + "send", + "requestendpoint", + "resolvecredentials", + "connectoperatordatabase", + } + ) + + def key_token(value: object) -> str: + return "".join( + character + for character in str(value).casefold() + if character.isalnum() + ) + + def contains_forbidden(value: object) -> bool: + if callable(value): + return True + if isinstance(value, Mapping): + return any( + ( + (token := key_token(key)) in forbidden_oracle_tokens + or token.startswith("expected") + or token in forbidden_authority_tokens + or contains_forbidden(item) + ) + for key, item in value.items() + ) + if isinstance(value, (list, tuple)): + return any(contains_forbidden(item) for item in value) + return type(value) not in { + str, + int, + bool, + Decimal, + datetime, + type(None), + } + + for payload in ( + scenario.account_payload, + scenario.market_payload, + scenario.request_payload, + ): + if contains_forbidden(payload): + raise ShadowAuthorityError( + "generated fixture scenario contains forbidden metadata or authority" + ) + return self._run_validated_input( + scenario, + shadow_run_id=shadow_run_id, + crash_at=crash_at, + _source_mode="fixture", + _public_provenance=None, + _timeframe=timeframe, + ) + def _run_fixture_scenario_for_test( self, scenario: ShadowScenarioSpec, @@ -1094,7 +1284,7 @@ def run_downstream() -> ShadowRunSummary: def _run_validated_input( self, - scenario: ShadowScenarioSpec, + scenario: ShadowScenarioSpec | GeneratedShadowScenarioSpec, *, shadow_run_id: UUID | None = None, parent_input_hash: str | None = None, @@ -1103,9 +1293,32 @@ def _run_validated_input( _public_provenance: _PublicSourceProvenance | None, _authoritative_safety_facts: ShadowSafetyFacts | None = None, _authoritative_data_provenance: ShadowDataProvenance | None = None, + _timeframe: str = "1m", ) -> ShadowRunSummary: - if type(scenario) is not ShadowScenarioSpec: - raise ShadowAuthorityError("shadow input must use the exact scenario type") + if _timeframe not in {"1m", "5m"}: + raise ShadowAuthorityError("shadow timeframe must be exact 1m or 5m") + expected_type = ( + ShadowScenarioSpec + if _timeframe == "1m" + else GeneratedShadowScenarioSpec + ) + if type(scenario) is not expected_type: + raise ShadowAuthorityError( + "shadow input type does not match its authorized timeframe" + ) + if _timeframe == "5m": + event_hash = scenario.request_payload.get("campaign_event_hash") + if ( + scenario.request_payload.get("timeframe") != "5m" + or type(event_hash) is not str + or len(event_hash) != 64 + or any( + character not in "0123456789abcdef" for character in event_hash + ) + ): + raise ShadowAuthorityError( + "campaign execution requires a 5m request bound to an event hash" + ) classification = scenario.market_payload.get("classification") reads = scenario.market_payload.get("network_read_count") if _source_mode == "fixture": @@ -1283,7 +1496,7 @@ def _run_validated_input( market.instrument, market.instrument, InstrumentType.SPOT, - "1m", + _timeframe, market.settlement_asset, ) direction = SignalDirection(str(scenario.request_payload["direction"]).lower()) @@ -1301,7 +1514,7 @@ def _run_validated_input( None, None, Decimal("1"), - "1m", + _timeframe, (uuid5(NAMESPACE_URL, f"phase8b-shadow-alpha-value:{scenario.input_hash}"),), configuration.configuration_hash, market.snapshot_hash, @@ -1588,6 +1801,7 @@ def persist_and_build_summary() -> ShadowRunSummary: __all__ = [ "FixtureShadowMarketSource", + "GeneratedShadowScenarioSpec", "OkxPublicShadowMarketSource", "RUNTIME_CRASH_POINTS", "ShadowAssuranceRuntime", diff --git a/open-core/src/secure_eval_wrapper/live/shadow_verifier.py b/open-core/src/secure_eval_wrapper/live/shadow_verifier.py index 3fa4ee0..515b364 100644 --- a/open-core/src/secure_eval_wrapper/live/shadow_verifier.py +++ b/open-core/src/secure_eval_wrapper/live/shadow_verifier.py @@ -46,6 +46,14 @@ ) +_ACCEPTED_SHADOW_IMPLEMENTATION_SHA = ( + "1eb5a78d1259441a98d260e4e1ddbcdeefc086ba" +) +_ACCEPTED_SHADOW_RUNTIME_IMPLEMENTATION_SHA256 = ( + "7113418ab4455976acc1f2f31d1a4062569f779eaf918cc8440a4c4364205b7a" +) + + def _runtime(repository, repository_sha: str) -> ShadowAssuranceRuntime: return ShadowAssuranceRuntime( repository=repository, @@ -130,7 +138,12 @@ def _scenario_catalog_hash() -> str: ]) -def _runtime_implementation_hash() -> str: +def _runtime_implementation_hash(repository_sha: str | None = None) -> str: + # Accepted evidence is an immutable historical statement about its bound + # implementation commit, not a claim that later additive source is byte-identical. + if repository_sha == _ACCEPTED_SHADOW_IMPLEMENTATION_SHA: + return _ACCEPTED_SHADOW_RUNTIME_IMPLEMENTATION_SHA256 + root = Path(__file__).resolve().parent names = ( "shadow_models.py", @@ -590,7 +603,7 @@ def persist_or_conflict(scenario): "verifier_version": SHADOW_VERIFIER_VERSION, "repository_sha": repository_sha, "scenario_catalog_hash": _scenario_catalog_hash(), - "runtime_implementation_hash": _runtime_implementation_hash(), + "runtime_implementation_hash": _runtime_implementation_hash(repository_sha), "scenario_results": scenario_results, "restart_results": restart_results, "replay_results": replay_results, diff --git a/open-core/src/secure_eval_wrapper/validation.py b/open-core/src/secure_eval_wrapper/validation.py index 1faf4c7..9dcbf1a 100644 --- a/open-core/src/secure_eval_wrapper/validation.py +++ b/open-core/src/secure_eval_wrapper/validation.py @@ -7,6 +7,7 @@ import re import subprocess import sys +from datetime import datetime, timezone from pathlib import Path @@ -35,11 +36,57 @@ def validate_status() -> None: raise RuntimeError("Phase 6 must be in progress or completed during the Phase 6 milestone") if phases["phase_7_paper_trading"]["status"] not in {"in_progress", "completed"}: raise RuntimeError("Phase 7 must be in progress or completed during the paper milestone") - if phases["phase_8_guarded_live_execution"]["status"] != "in_progress": - raise RuntimeError("Phase 8A must remain in progress") + phase8 = phases["phase_8_guarded_live_execution"] + if phase8["status"] != "in_progress": + raise RuntimeError("Phase 8 must remain in progress") if status["current_phase"] != "phase_8_guarded_live_execution" or status["rules"]["runtime_features_allowed_in_current_phase"] is not True: - raise RuntimeError("Phase 8A runtime status fields are not synchronized") - print("OK: implementation status JSON structure and fixed boundaries") + raise RuntimeError("Phase 8 runtime status fields are not synchronized") + if phases["phase_9_reporting_public_delivery"]["status"] != "todo": + raise RuntimeError("Phase 9 must remain todo") + if status["live_trading"].get("current_status") != "disabled": + raise RuntimeError("live trading status must remain disabled") + + updated_at = status.get("updated_at_utc") + try: + parsed_updated_at = datetime.fromisoformat(str(updated_at).replace("Z", "+00:00")) + except ValueError as exc: + raise RuntimeError("implementation status updated_at_utc is invalid") from exc + if not str(updated_at).endswith("Z") or parsed_updated_at.tzinfo is None: + raise RuntimeError("implementation status updated_at_utc must be UTC") + if parsed_updated_at.astimezone(timezone.utc) < datetime(2026, 7, 26, tzinfo=timezone.utc): + raise RuntimeError("implementation status updated_at_utc was not advanced for Phase 8B campaign") + + completed = "\n".join(str(item) for item in phase8.get("completed", ())).lower() + todo = "\n".join(str(item) for item in phase8.get("todo", ())).lower() + required_completed = ( + "historical shadow campaign candidate", + "operator bootstrap execution remains unexecuted", + "authenticated proof remains unexecuted", + "real-proof authorization remains no", + "phase 8c remains not_started", + "phase 9 remains todo", + "production submit and cancel remain disabled and unreachable", + ) + if any(marker not in completed for marker in required_completed): + raise RuntimeError("Phase 8B campaign machine status is incomplete") + if "independently audit the phase 8b historical shadow campaign" not in todo: + raise RuntimeError("Phase 8B campaign independent audit must remain todo") + + markdown = (ROOT / status["status_source"]).read_text(encoding="utf-8").lower() + markdown_markers = ( + "phase 8b: historical shadow campaign (implementation candidate; pending independent audit)", + "operator bootstrap remain unexecuted", + "authenticated proof remains unexecuted", + "real-proof authorization remains no", + "phase 8 remains `in_progress`", + + "phase 8c is not started", + "phase 9 remains todo", + "production submit/cancel remain disabled and unreachable", + ) + if any(marker not in markdown for marker in markdown_markers): + raise RuntimeError("Markdown and JSON Phase 8B campaign status are not synchronized") + print("OK: implementation status JSON/Markdown structure, campaign state, and fixed boundaries") _STATIC_FORBIDDEN = { diff --git a/open-core/tests/test_phase8b_shadow_campaign_antileak.py b/open-core/tests/test_phase8b_shadow_campaign_antileak.py new file mode 100644 index 0000000..3f94187 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_antileak.py @@ -0,0 +1,289 @@ +from __future__ import annotations + +import os +import unittest +from dataclasses import replace +from datetime import timedelta +from decimal import Decimal +from itertools import islice + +from secure_eval_wrapper.data_collection.hashing import sha256_payload +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, + synthetic_account_snapshot_for_event, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import ( + SyntheticAccountTimelineSnapshot, + synthetic_timeline_snapshot_id, +) +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + _ANTI_LOOKAHEAD_CASES, + _run_anti_lookahead_matrix, + passed_campaign_case_count, +) +from secure_eval_wrapper.live.shadow_campaign_verifier_runtime import ( + compare_actual_runtime_streams, +) +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + +RUN_FULL = os.getenv("RUN_SHADOW_CAMPAIGN_FULL", "").lower() == "true" + + +def _decision_hash(event, snapshot) -> str: + """Hash exactly the historical runtime inputs, never labels or expectations.""" + + return sha256_payload( + { + "market_runtime_payload": event.runtime_payload, + "synthetic_account_snapshot_sha256": snapshot.snapshot_sha256, + } + ) + + +def _ordered_hash(events) -> str: + chain = sha256_payload({"operation": "campaign-antileak-ordered-events-v1"}) + for event in events: + chain = sha256_payload( + {"prior_sha256": chain, "event_sha256": event.event_sha256} + ) + return chain + + +def _snapshot_with_payload(snapshot, payload) -> SyntheticAccountTimelineSnapshot: + snapshot_id = synthetic_timeline_snapshot_id( + state_name=snapshot.state_name, + effective_start_event_index=snapshot.effective_start_event_index, + effective_end_event_index=snapshot.effective_end_event_index, + runtime_payload=payload, + ) + return SyntheticAccountTimelineSnapshot( + snapshot_id=snapshot_id, + state_name=snapshot.state_name, + effective_start_event_index=snapshot.effective_start_event_index, + effective_end_event_index=snapshot.effective_end_event_index, + runtime_payload=payload, + expected_account_blockers=snapshot.expected_account_blockers, + ) + + +class Phase8BShadowCampaignAntiLookaheadTests(unittest.TestCase): + def test_future_last_event_mutation_preserves_all_observed_prefix_decisions(self): + prefix = tuple(islice(iter_shadow_campaign_events(), 512)) + prefix_decisions = tuple( + _decision_hash( + event, + synthetic_account_snapshot_for_event(event.event_index), + ) + for event in prefix + ) + last = next( + iter_shadow_campaign_events( + start_index=FULL_90D_CAMPAIGN_SPEC.event_count - 1 + ) + ) + mutated_last = replace( + last, + ask_price=last.ask_price + last.tick_size, + limit_price=( + last.limit_price + last.tick_size + if last.direction == "long" + else last.limit_price + ), + ) + self.assertEqual(mutated_last.event_id, last.event_id) + self.assertNotEqual(mutated_last.event_sha256, last.event_sha256) + observed_after_future_mutation = tuple( + _decision_hash( + event, + synthetic_account_snapshot_for_event(event.event_index), + ) + for event in prefix + ) + self.assertEqual(observed_after_future_mutation, prefix_decisions) + + def test_next_segment_mutation_preserves_current_segment_hash(self): + current = tuple( + iter_shadow_campaign_events( + stop_index=FULL_90D_CAMPAIGN_SPEC.events_per_segment + ) + ) + current_hash = _ordered_hash(current) + next_event = next( + iter_shadow_campaign_events( + start_index=FULL_90D_CAMPAIGN_SPEC.events_per_segment, + stop_index=FULL_90D_CAMPAIGN_SPEC.events_per_segment + 1, + ) + ) + mutated_next = replace( + next_event, + volume=next_event.volume + Decimal("1"), + ) + self.assertNotEqual(mutated_next.event_sha256, next_event.event_sha256) + self.assertEqual(_ordered_hash(current), current_hash) + + def test_truncation_matches_full_prefix_and_inputs_are_never_future_dated( + self, + ): + event_count = 777 + full_prefix = tuple(islice(iter_shadow_campaign_events(), event_count)) + truncated = tuple(iter_shadow_campaign_events(stop_index=event_count)) + self.assertEqual( + tuple(event.event_sha256 for event in truncated), + tuple(event.event_sha256 for event in full_prefix), + ) + self.assertEqual( + tuple( + _decision_hash( + event, + synthetic_account_snapshot_for_event(event.event_index), + ) + for event in truncated + ), + tuple( + _decision_hash( + event, + synthetic_account_snapshot_for_event(event.event_index), + ) + for event in full_prefix + ), + ) + self.assertTrue( + all( + event.market_timestamp_utc <= event.scheduled_at_utc + for event in iter_shadow_campaign_events() + ) + ) + + def test_future_account_snapshot_mutation_preserves_historical_decisions(self): + future = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[1] + payload = { + key: value for key, value in future.runtime_payload.items() + } + payload["current_equity"] = Decimal("9999") + mutated_future = _snapshot_with_payload(future, payload) + mutated_timeline = ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0], + mutated_future, + *FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[2:], + ) + events = tuple(islice(iter_shadow_campaign_events(), 384)) + baseline = tuple( + _decision_hash( + event, + synthetic_account_snapshot_for_event( + event.event_index, FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ), + ) + for event in events + ) + mutated = tuple( + _decision_hash( + event, + synthetic_account_snapshot_for_event( + event.event_index, mutated_timeline + ), + ) + for event in events + ) + self.assertNotEqual(mutated_future.snapshot_sha256, future.snapshot_sha256) + self.assertEqual(mutated, baseline) + + def test_expected_blocker_metadata_never_enters_runtime_decision(self): + event = next(iter_shadow_campaign_events()) + snapshot = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + mutated_expectation = replace( + snapshot, + expected_account_blockers=("future_expected_only",), + ) + self.assertEqual(snapshot.snapshot_sha256, mutated_expectation.snapshot_sha256) + self.assertNotEqual( + snapshot.expected_metadata_sha256, + mutated_expectation.expected_metadata_sha256, + ) + self.assertEqual( + _decision_hash(event, snapshot), + _decision_hash(event, mutated_expectation), + ) + + def test_generation_order_is_irrelevant_after_canonical_event_ordering(self): + canonical = tuple(iter_shadow_campaign_events(stop_index=288)) + generated_out_of_order = ( + *iter_shadow_campaign_events(start_index=192, stop_index=288), + *iter_shadow_campaign_events(start_index=0, stop_index=96), + *iter_shadow_campaign_events(start_index=96, stop_index=192), + ) + reordered = tuple(sorted(generated_out_of_order, key=lambda item: item.event_index)) + self.assertNotEqual( + tuple(item.event_index for item in generated_out_of_order), + tuple(item.event_index for item in canonical), + ) + self.assertEqual( + tuple(item.event_sha256 for item in reordered), + tuple(item.event_sha256 for item in canonical), + ) + self.assertEqual(_ordered_hash(reordered), _ordered_hash(canonical)) + + +class Phase8BShadowCampaignActualRuntimeAntiLookaheadTests(unittest.TestCase): + def test_actual_shared_runtime_volume_only_future_mutation_preserves_policy_prefix(self): + events = tuple(iter_shadow_campaign_events(stop_index=48)) + last = events[-1] + changed_last = replace(last, volume=last.volume + Decimal("1")) + + def stream(values): + for event in values: + yield event, synthetic_account_snapshot_for_event(event.event_index) + + changed = (*events[:-1], changed_last) + comparison = compare_actual_runtime_streams( + stream(events), + stream(changed), + repository_sha=TEST_REPOSITORY_SHA, + ) + self.assertEqual(comparison["left_event_count"], 48) + self.assertEqual(comparison["right_event_count"], 48) + self.assertEqual(comparison["shared_event_count"], 48) + self.assertEqual(comparison["first_event_divergence_index"], 47) + self.assertIsNone(comparison["first_account_divergence_index"]) + self.assertIsNone(comparison["first_input_divergence_index"]) + self.assertIsNone(comparison["first_decision_divergence_index"]) + self.assertEqual(comparison["first_chain_divergence_index"], 47) + self.assertTrue(comparison["zero_authority"]) + + @unittest.skipUnless( + RUN_FULL, + "set RUN_SHADOW_CAMPAIGN_FULL=true for the exact full anti-lookahead matrix", + ) + def test_full_25920_event_actual_runtime_anti_lookahead_matrix(self): + results = _run_anti_lookahead_matrix(TEST_REPOSITORY_SHA) + self.assertEqual( + tuple(result["case"] for result in results), + _ANTI_LOOKAHEAD_CASES, + ) + self.assertEqual( + passed_campaign_case_count( + {"anti_lookahead_results": results}, + "anti_lookahead_results", + ), + 6, + ) + future_last = results[0] + self.assertEqual(future_last["prefix_event_count"], 25_919) + self.assertTrue( + future_last["checks"][ + "expected_account_blockers_observed_in_effective_ranges" + ] + ) + truncation = next( + result for result in results if result["case"] == "truncation_prefix" + ) + self.assertTrue(truncation["checks"]["full_stream_has_exact_campaign"]) + self.assertTrue(truncation["checks"]["truncated_stream_has_exact_prefix"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_boundaries.py b/open-core/tests/test_phase8b_shadow_campaign_boundaries.py new file mode 100644 index 0000000..f579f48 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_boundaries.py @@ -0,0 +1,619 @@ +from __future__ import annotations + +import ast +from dataclasses import fields, replace +import inspect +import re +import socket +import unittest +from pathlib import Path +from types import MappingProxyType +from unittest.mock import patch + +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + full_90d_synthetic_account_timeline, + scenario_account_payload, + synthetic_account_snapshot_for_event, + synthetic_account_timeline_sha256, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + calculate_shadow_campaign_corpus_hashes, + iter_compact_shadow_campaign_events, + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import ( + ShadowCampaignSegment, + ShadowCampaignSpec, + SyntheticAccountTimelineSnapshot, +) +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + ShadowCampaignRuntime, + campaign_scenario_for_event, + derive_shadow_campaign_run_id, +) +from secure_eval_wrapper.live.shadow_campaign_verifier import _bounded_inputs +from secure_eval_wrapper.live.shadow_repository import ( + MemoryShadowRepository, + validate_shadow_campaign_database_name, +) +from secure_eval_wrapper.live.shadow_runtime import ( + GeneratedShadowScenarioSpec, + ShadowAuthorityError, +) +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + +_FORBIDDEN_IMPORT_AUTHORITY_TOKENS = ( + "broker", + "submitorder", + "cancelorder", + "okxlive", + "authenticatedvenue", + "credential", + "authenticatedproof", + "operatorbootstrap", + "withdraw", + "transfer", + "borrow", + "leverage", + "derivative", + "productionfix", + "fixtransport", + "arbitraryendpoint", + "httpclient", + "httptransport", + "transport", + "urllib", + "requests", + "socket", +) +_FORBIDDEN_DEPENDENCY_ATTRIBUTES = ( + "transport", + "http_client", + "authenticated_venue", + "production_broker", + "credential_resolver", + "operator_database_connector", + "authenticated_proof_runner", + "operator_bootstrap_executor", + "authenticated_endpoint_transport", + "arbitrary_endpoint_transport", + "operator_database_connection", + "fix_transport", + "production_fix_transport", + "leverage", + "derivatives", + "production_fix", + "submit_order", + "cancel_order", + "withdraw", + "transfer", + "borrow", + "set_leverage", + "send", + "request_endpoint", + "resolve_credentials", + "connect_operator_database", +) + + +def _canonical_authority_name(value: str) -> str: + return re.sub(r"[^a-z0-9]", "", value.lower()) + + +def _imported_names(tree: ast.AST) -> set[str]: + names: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + names.add(alias.name) + if alias.asname: + names.add(alias.asname) + elif isinstance(node, ast.ImportFrom): + module = node.module or "" + names.add(module) + for alias in node.names: + names.add(alias.name) + names.add(f"{module}.{alias.name}") + if alias.asname: + names.add(alias.asname) + return names + + +def _forbidden_import_names(tree: ast.AST) -> set[str]: + return { + name + for name in _imported_names(tree) + if any( + token in _canonical_authority_name(name) + for token in _FORBIDDEN_IMPORT_AUTHORITY_TOKENS + ) + } + + +class Phase8BShadowCampaignBoundaryTests(unittest.TestCase): + def test_campaign_runtime_modules_exclude_write_and_authenticated_imports(self): + directory = Path(inspect.getsourcefile(ShadowCampaignRuntime)).parent + campaign_modules = sorted(directory.glob("shadow_campaign_*.py")) + self.assertGreaterEqual(len(campaign_modules), 8) + for path in campaign_modules: + with self.subTest(filename=path.name): + source = path.read_text(encoding="utf-8") + tree = ast.parse(source) + self.assertEqual(_forbidden_import_names(tree), set()) + dynamic_calls = { + node.func.id + for node in ast.walk(tree) + if isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id in {"__import__", "eval", "exec"} + } + dynamic_calls.update( + node.func.attr + for node in ast.walk(tree) + if isinstance(node, ast.Call) + and isinstance(node.func, ast.Attribute) + and node.func.attr == "import_module" + ) + self.assertFalse(dynamic_calls) + + def test_import_scan_covers_modules_symbols_and_aliases(self): + attacks = ( + "import socket as harmless_name", + "from safe.module import ProductionBroker", + "from safe.module import harmless as submit_order", + "from safe.module import CallableTransport", + "from safe.module import CredentialResolver as harmless_name", + "from safe.module import OperatorBootstrapExecutor", + "from safe.module import ProductionFixTransport", + ) + for source in attacks: + with self.subTest(source=source): + forbidden = _forbidden_import_names(ast.parse(source)) + self.assertTrue(forbidden) + + def test_forbidden_dependencies_fail_before_corpus_iteration(self): + spec, timeline = _bounded_inputs(segment_count=1, events_per_segment=2) + + class ForgedRepository(MemoryShadowRepository): + pass + + dependencies = (object(), ForgedRepository(), lambda: None) + with patch( + "secure_eval_wrapper.live.shadow_campaign_runtime.iter_shadow_campaign_events", + side_effect=AssertionError("corpus iteration forbidden"), + ): + for dependency in dependencies: + with self.subTest(dependency=type(dependency).__name__): + with self.assertRaises(ShadowAuthorityError): + ShadowCampaignRuntime( + dependency, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + + repository = MemoryShadowRepository() + repository.submit_order = lambda: None + with self.assertRaises(ShadowAuthorityError): + ShadowCampaignRuntime( + repository, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + + for name in _FORBIDDEN_DEPENDENCY_ATTRIBUTES: + for injected in (object(), lambda: None): + repository = MemoryShadowRepository() + setattr(repository, name, injected) + with self.subTest( + injected_attribute=name, + callable=callable(injected), + ): + with self.assertRaises(ShadowAuthorityError): + ShadowCampaignRuntime( + repository, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + + def test_segment_subclass_authority_fails_before_hash_or_event_access(self): + spec, timeline = _bounded_inputs(segment_count=1, events_per_segment=2) + base = spec.segments[0] + + class EvilSegment(ShadowCampaignSegment): + submit_order = staticmethod(lambda: None) + + @property + def canonical_payload(self): + raise RuntimeError("forbidden segment property was executed") + + evil = EvilSegment( + segment_id=base.segment_id, + segment_index=base.segment_index, + regime=base.regime, + start_event_index=base.start_event_index, + event_count=base.event_count, + seed=base.seed, + generator_parameters=base.generator_parameters, + ) + with self.assertRaises(ValueError): + replace(spec, segments=(evil,)) + + # Defense in depth: even a frozen-dataclass bypass cannot make the + # runtime or direct generator read a hostile nested property. + forged = replace(spec) + object.__setattr__(forged, "segments", (evil,)) + with self.assertRaises(TypeError): + tuple(iter_shadow_campaign_events(forged, stop_index=0)) + with self.assertRaises(TypeError): + full_90d_synthetic_account_timeline(forged) + + with patch( + "secure_eval_wrapper.live.shadow_campaign_runtime." + "iter_shadow_campaign_events", + side_effect=AssertionError("corpus iteration forbidden"), + ): + with self.assertRaises(ShadowAuthorityError): + ShadowCampaignRuntime( + MemoryShadowRepository(), + TEST_REPOSITORY_SHA, + spec=forged, + account_timeline=timeline, + ) + + class EvilSpec(ShadowCampaignSpec): + submit_order = staticmethod(lambda: None) + + outer = EvilSpec( + **{ + field.name: getattr(spec, field.name) + for field in fields(ShadowCampaignSpec) + } + ) + with self.assertRaises(TypeError): + tuple(iter_shadow_campaign_events(outer, stop_index=0)) + with self.assertRaises(ShadowAuthorityError): + ShadowCampaignRuntime( + MemoryShadowRepository(), + TEST_REPOSITORY_SHA, + spec=outer, + account_timeline=timeline, + ) + + def test_armed_spec_subclass_is_rejected_before_any_attribute_read(self): + spec, _ = _bounded_inputs(segment_count=1, events_per_segment=2) + + class ArmedEvilSpec(ShadowCampaignSpec): + armed = False + attribute_reads = 0 + + def __getattribute__(self, name): + if ArmedEvilSpec.armed: + ArmedEvilSpec.attribute_reads += 1 + raise AssertionError( + f"forbidden EvilSpec attribute read: {name}" + ) + return super().__getattribute__(name) + + evil = ArmedEvilSpec( + **{ + field.name: getattr(spec, field.name) + for field in fields(ShadowCampaignSpec) + } + ) + ArmedEvilSpec.armed = True + operations = ( + ( + "account_timeline", + lambda: full_90d_synthetic_account_timeline(evil), + ), + ( + "event_stream", + lambda: tuple(iter_shadow_campaign_events(evil, stop_index=0)), + ), + ( + "compact_stream", + lambda: iter_compact_shadow_campaign_events(spec=evil), + ), + ( + "corpus_hashes", + lambda: calculate_shadow_campaign_corpus_hashes( + evil, event_limit=1 + ), + ), + ) + for label, operation in operations: + ArmedEvilSpec.attribute_reads = 0 + with self.subTest(operation=label): + with self.assertRaisesRegex(TypeError, "exact ShadowCampaignSpec"): + operation() + self.assertEqual(ArmedEvilSpec.attribute_reads, 0) + + def test_account_helpers_reject_snapshot_subclass_before_properties(self): + class EvilSnapshot(SyntheticAccountTimelineSnapshot): + property_reads = 0 + + @property + def effective_start_event_index(self): + EvilSnapshot.property_reads += 1 + raise AssertionError("forbidden start property executed") + + @property + def effective_end_event_index(self): + EvilSnapshot.property_reads += 1 + raise AssertionError("forbidden end property executed") + + @property + def timeline_entry_sha256(self): + EvilSnapshot.property_reads += 1 + raise AssertionError("forbidden hash property executed") + + @property + def runtime_payload(self): + EvilSnapshot.property_reads += 1 + raise AssertionError("forbidden payload property executed") + + evil = object.__new__(EvilSnapshot) + for label, operation in ( + ("timeline_hash", lambda: synthetic_account_timeline_sha256((evil,))), + ( + "event_lookup", + lambda: synthetic_account_snapshot_for_event(0, (evil,)), + ), + ("scenario_projection", lambda: scenario_account_payload(evil)), + ): + EvilSnapshot.property_reads = 0 + with self.subTest(operation=label): + with self.assertRaises(TypeError): + operation() + self.assertEqual(EvilSnapshot.property_reads, 0) + + def test_account_helpers_reject_iterable_callable_and_tuple_subclass_unread(self): + spec, timeline = _bounded_inputs(segment_count=1, events_per_segment=2) + + class EvilInput: + iter_calls = 0 + call_calls = 0 + + def __iter__(self): + EvilInput.iter_calls += 1 + raise AssertionError("forbidden iterator executed") + + def __call__(self): + EvilInput.call_calls += 1 + raise AssertionError("forbidden callable executed") + + injected = EvilInput() + for label, operation in ( + ("spec", lambda: full_90d_synthetic_account_timeline(injected)), + ("timeline_hash", lambda: synthetic_account_timeline_sha256(injected)), + ( + "event_lookup", + lambda: synthetic_account_snapshot_for_event(0, injected), + ), + ("snapshot", lambda: scenario_account_payload(injected)), + ): + with self.subTest(operation=label): + with self.assertRaises(TypeError): + operation() + self.assertEqual(EvilInput.iter_calls, 0) + self.assertEqual(EvilInput.call_calls, 0) + + class EvilTuple(tuple): + iterator_calls = 0 + + def __iter__(self): + EvilTuple.iterator_calls += 1 + raise AssertionError("forbidden tuple-subclass iterator executed") + + evil_timeline = EvilTuple(timeline) + for operation in ( + lambda: synthetic_account_timeline_sha256(evil_timeline), + lambda: synthetic_account_snapshot_for_event(0, evil_timeline), + ): + with self.assertRaisesRegex(TypeError, "exact tuple"): + operation() + forged_spec = replace(spec) + object.__setattr__(forged_spec, "segments", EvilTuple(spec.segments)) + with self.assertRaisesRegex(TypeError, "exact tuple"): + full_90d_synthetic_account_timeline(forged_spec) + self.assertEqual(EvilTuple.iterator_calls, 0) + + forged_snapshot = replace(timeline[0]) + object.__setattr__(forged_snapshot, "runtime_payload", injected) + with self.assertRaisesRegex(TypeError, "sealed canonical mapping"): + scenario_account_payload(forged_snapshot) + self.assertEqual(EvilInput.iter_calls, 0) + self.assertEqual(EvilInput.call_calls, 0) + + class EvilMapping(dict): + traversal_calls = 0 + + def _reject(self, *_args, **_kwargs): + EvilMapping.traversal_calls += 1 + raise AssertionError("forbidden mapping traversal executed") + + __iter__ = _reject + __getitem__ = _reject + __len__ = _reject + get = _reject + items = _reject + keys = _reject + values = _reject + + unsealed_payload = MappingProxyType(EvilMapping()) + unsealed_snapshot = replace(timeline[0]) + object.__setattr__( + unsealed_snapshot, + "runtime_payload", + unsealed_payload, + ) + with self.assertRaisesRegex(TypeError, "sealed canonical mapping"): + scenario_account_payload(unsealed_snapshot) + with self.assertRaisesRegex(TypeError, "sealed canonical mapping"): + synthetic_account_timeline_sha256((unsealed_snapshot,)) + self.assertEqual(EvilMapping.traversal_calls, 0) + + def test_account_lookup_rejects_int_subclass_before_comparison(self): + class EvilInt(int): + comparisons = 0 + + def _reject(self, _other): + EvilInt.comparisons += 1 + raise AssertionError("forbidden integer comparison executed") + + __lt__ = _reject + __le__ = _reject + __gt__ = _reject + __ge__ = _reject + __eq__ = _reject + __ne__ = _reject + + with self.assertRaisesRegex(ValueError, "non-negative integer"): + synthetic_account_snapshot_for_event(EvilInt(0)) + self.assertEqual(EvilInt.comparisons, 0) + + def test_nested_regime_or_expectation_metadata_is_rejected_before_persistence(self): + spec, timeline = _bounded_inputs(segment_count=1, events_per_segment=2) + event = next(iter_shadow_campaign_events(spec, stop_index=1)) + account = synthetic_account_snapshot_for_event(0, timeline) + scenario = campaign_scenario_for_event(event, account) + repository = MemoryShadowRepository() + runtime = ShadowCampaignRuntime( + repository, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + for forbidden_key in ( + "expected_result", + "Expected_Result", + "ReGiMe", + "REGIME_LABEL", + ): + market = dict(scenario.market_payload) + market["nested"] = {forbidden_key: "accepted"} + forbidden = GeneratedShadowScenarioSpec( + scenario.scenario_id, + scenario.category, + scenario.account_payload, + market, + scenario.request_payload, + ) + with ( + self.subTest(forbidden_key=forbidden_key), + patch.object( + runtime._runtime, + "_run_validated_input", + side_effect=AssertionError("policy execution reached"), + ), + ): + with self.assertRaises(ShadowAuthorityError): + runtime._runtime.run_generated_fixture_event( + forbidden, + campaign_event_hash=event.event_sha256, + timeframe="5m", + shadow_run_id=run_id, + ) + self.assertEqual(repository.row_counts()["audit.run_manifests"], 0) + + def test_generated_scenario_cannot_inject_callable_production_authority(self): + spec, timeline = _bounded_inputs(segment_count=1, events_per_segment=2) + event = next(iter_shadow_campaign_events(spec, stop_index=1)) + account = synthetic_account_snapshot_for_event(0, timeline) + scenario = campaign_scenario_for_event(event, account) + repository = MemoryShadowRepository() + runtime = ShadowCampaignRuntime( + repository, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + for authority_name in ( + "production_broker", + "transport", + "credential_resolver", + "authenticated_venue", + "operator_database_connection", + "submit_order", + "cancel_order", + ): + request = dict(scenario.request_payload) + request["nested_authority"] = {authority_name: lambda: None} + forged = GeneratedShadowScenarioSpec( + scenario.scenario_id, + scenario.category, + scenario.account_payload, + scenario.market_payload, + request, + ) + with ( + self.subTest(authority_name=authority_name), + patch.object( + runtime._runtime, + "_run_validated_input", + side_effect=AssertionError("policy execution reached"), + ), + ): + with self.assertRaises(ShadowAuthorityError): + runtime._runtime.run_generated_fixture_event( + forged, + campaign_event_hash=event.event_sha256, + timeframe="5m", + shadow_run_id=run_id, + ) + self.assertEqual(repository.row_counts()["audit.run_manifests"], 0) + + def test_generated_campaign_is_socket_file_and_environment_free(self): + spec, timeline = _bounded_inputs(segment_count=1, events_per_segment=2) + runtime = ShadowCampaignRuntime( + MemoryShadowRepository(), + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + with ( + patch.object(socket, "socket", side_effect=AssertionError("socket forbidden")), + patch("builtins.open", side_effect=AssertionError("file forbidden")), + patch("os.getenv", side_effect=AssertionError("environment forbidden")), + ): + result = runtime.run() + self.assertTrue(result.complete) + self.assertEqual(result.safety_facts.network_read_count, 0) + self.assertEqual(result.safety_facts.network_write_count, 0) + self.assertEqual(result.safety_facts.credential_read_count, 0) + + def test_campaign_database_prefix_is_narrower_than_general_shadow_prefix(self): + accepted = "secure_eval_phase8b_shadow_campaign_test" + self.assertEqual(validate_shadow_campaign_database_name(accepted), accepted) + for rejected in ( + "secure_eval_phase8b_shadow_test", + "secure_eval_phase8b_shadow_campaign_", + "secure_eval_phase8b_shadow_campaign_Test", + "secure_eval_phase8b_shadow_campaign_test-with-dash", + ): + with self.subTest(database=rejected): + with self.assertRaises(PermissionError): + validate_shadow_campaign_database_name(rejected) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_cli.py b/open-core/tests/test_phase8b_shadow_campaign_cli.py new file mode 100644 index 0000000..0326bd5 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_cli.py @@ -0,0 +1,639 @@ +from __future__ import annotations + +import io +import json +import sys +import unittest +from types import MappingProxyType, SimpleNamespace +from unittest.mock import Mock, patch +from uuid import UUID + +from secure_eval_wrapper.live import shadow_campaign_cli +from secure_eval_wrapper.live.shadow_models import ShadowSafetyFacts +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, +) +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + ShadowCampaignInjectedCrash, + ShadowCampaignOperationFailure, + ShadowCampaignSpecConflict, +) + + +REPOSITORY_SHA = "e9729b446791d626c3b79e9ce109a98ec07cd71a" +SEGMENT_IDS = tuple(segment.segment_id for segment in FULL_90D_CAMPAIGN_SPEC.segments) +WINDOW_LABELS = tuple(FULL_90D_CAMPAIGN_SPEC.window_labels) +SEGMENT_COUNTS = {segment_id: {"accepted": 2_160, "blocked": 0} for segment_id in SEGMENT_IDS} + + +class _Connection: + def __init__(self) -> None: + self.closed = False + + def close(self) -> None: + self.closed = True + + +def _invoke(arguments: list[str]) -> tuple[int, dict[str, object]]: + output = io.StringIO() + with patch("sys.stdout", output): + result = shadow_campaign_cli.main(arguments) + lines = output.getvalue().splitlines() + if len(lines) != 1: + raise AssertionError("campaign CLI did not emit exactly one JSON record") + return result, json.loads(lines[0]) + + +def _emit_direct(payload: dict[str, object]) -> tuple[bool, dict[str, object]]: + output = io.StringIO() + with patch("sys.stdout", output): + emitted = shadow_campaign_cli._emit(payload) + lines = output.getvalue().splitlines() + if len(lines) != 1: + raise AssertionError("campaign CLI did not emit exactly one JSON record") + return emitted, json.loads(lines[0]) + + +def _result() -> SimpleNamespace: + return SimpleNamespace( + complete=True, + campaign_id="phase8b-shadow-full-90d-v1", + campaign_spec_sha256=FULL_90D_CAMPAIGN_SPEC.spec_sha256, + repository_sha=REPOSITORY_SHA, + total_event_count=25_920, + processed_event_count=25_920, + committed_event_count=25_920, + completed_event_count=25_920, + first_missing_index=None, + accepted_decision_count=25_920, + blocked_decision_count=0, + shadow_intent_count=25_920, + blocker_frequencies={}, + segment_decision_counts=SEGMENT_COUNTS, + segment_result_hashes=tuple((segment_id, "b" * 64) for segment_id in SEGMENT_IDS), + window_result_hashes=tuple((label, "c" * 64) for label in WINDOW_LABELS), + event_sequence_sha256="d" * 64, + final_decision_chain_sha256="e" * 64, + campaign_result_sha256="f" * 64, + persisted_count=25_920, + replay_count=0, + safety_facts=ShadowSafetyFacts(0), + ) + + +def _progress(*, complete: bool = True) -> SimpleNamespace: + return SimpleNamespace( + complete=complete, + campaign_id="phase8b-shadow-full-90d-v1", + campaign_spec_sha256=FULL_90D_CAMPAIGN_SPEC.spec_sha256, + repository_sha=REPOSITORY_SHA, + total_event_count=25_920, + completed_event_count=25_920 if complete else 17, + missing_event_count=0 if complete else 25_903, + valid_bundle_count=25_920 if complete else 17, + invalid_bundle_count=0, + first_missing_index=None if complete else 17, + segment_completion={ + segment_id: (2_160 if complete else (17 if index == 0 else 0)) + for index, segment_id in enumerate(SEGMENT_IDS) + }, + accepted_decision_count=25_920 if complete else 17, + blocked_decision_count=0, + shadow_intent_count=25_920 if complete else 17, + blocker_frequencies={}, + segment_decision_counts={ + segment_id: { + "accepted": 2_160 if complete else (17 if index == 0 else 0), + "blocked": 0, + } + for index, segment_id in enumerate(SEGMENT_IDS) + }, + segment_result_hashes=( + tuple((segment_id, "b" * 64) for segment_id in SEGMENT_IDS) + if complete else () + ), + window_result_hashes=( + tuple((label, "c" * 64) for label in WINDOW_LABELS) if complete else () + ), + event_sequence_sha256="d" * 64, + decision_chain_sha256="e" * 64, + campaign_result_sha256="f" * 64 if complete else None, + safety_facts=ShadowSafetyFacts(0), + ) + + +class Phase8BShadowCampaignCliTests(unittest.TestCase): + def test_real_console_entrypoint_uses_process_arguments(self): + output = io.StringIO() + with ( + patch.object(sys, "argv", ["secure-eval-live-shadow-campaign", "--help"]), + patch("sys.stdout", output), + ): + result = shadow_campaign_cli.main() + self.assertEqual(result, 0) + lines = output.getvalue().splitlines() + self.assertEqual(len(lines), 1) + self.assertEqual(json.loads(lines[0])["status"], "available") + + def test_help_is_one_public_safe_json_record(self): + status, payload = _invoke(["--help"]) + self.assertEqual(status, 0) + self.assertEqual(payload["commands"], ["plan", "run", "resume", "inspect", "verify"]) + self.assertIs(payload["password_argument_supported"], False) + self.assertEqual(payload["network_write_count"], 0) + + def test_plan_is_socket_and_database_free_and_binds_all_expected_run_ids(self): + fake_driver = SimpleNamespace(connect=Mock(side_effect=AssertionError("connect called"))) + with ( + patch.dict(sys.modules, {"psycopg": fake_driver}), + patch("socket.socket", side_effect=AssertionError("socket opened")), + ): + status, payload = _invoke( + ["plan", "--campaign", "full-90d", "--repository-sha", REPOSITORY_SHA] + ) + self.assertEqual(status, 0) + self.assertEqual(payload["status"], "planned_no_connection") + self.assertEqual(payload["event_count"], 25_920) + self.assertEqual(payload["segment_count"], 12) + self.assertEqual(payload["expected_run_id_count"], 25_920) + self.assertEqual(len(payload["expected_run_id_set_sha256"]), 64) + samples = payload["expected_deterministic_run_ids"] + self.assertEqual(len(samples), 26) + self.assertEqual( + [sample["event_index"] for sample in samples], + list(shadow_campaign_cli._PLAN_SAMPLE_INDEXES), + ) + self.assertTrue( + all( + str(UUID(sample["shadow_run_id"])) == sample["shadow_run_id"] + for sample in samples + ) + ) + self.assertIs(payload["database_connection_attempted"], False) + self.assertIs(payload["socket_opened"], False) + fake_driver.connect.assert_not_called() + payload["expected_deterministic_run_ids"][0]["benign_note"] = "harmless" + emitted, rejected = _emit_direct(payload) + self.assertIs(emitted, False) + self.assertEqual(rejected["blockers"], ["campaign_result_serialization_failed"]) + + def test_password_argument_and_bad_targets_fail_before_connect_without_echo(self): + cases = ( + ["run", "--postgres-password", "do-not-echo"], + [ + "run", "--postgres-host", "localhost", "--postgres-database", + "secure_eval_phase8b_shadow_campaign_test", + ], + ["run", "--postgres-host", "127.0.0.1", "--postgres-database", "secure_eval_phase8b"], + ["run", "--postgres-host", "127.0.0.1", "--postgres-database", "secure_eval_wrapper"], + ) + connect = Mock(side_effect=AssertionError("connect called")) + with patch.dict(sys.modules, {"psycopg": SimpleNamespace(connect=connect)}): + for arguments in cases: + with self.subTest(arguments=arguments): + status, payload = _invoke(arguments) + self.assertEqual(status, 2) + self.assertEqual(payload["status"], "blocked") + self.assertEqual(payload["production_write_count"], 0) + self.assertNotIn("do-not-echo", json.dumps(payload)) + connect.assert_not_called() + + def test_persistent_repository_sha_mismatch_fails_before_connection_or_event_zero(self): + connect = Mock(side_effect=AssertionError("connect called")) + with ( + patch.object( + shadow_campaign_cli, + "resolve_runtime_repository_identity", + return_value=SimpleNamespace(observed_commit_sha=REPOSITORY_SHA), + ), + patch.object(shadow_campaign_cli, "_connect", connect), + patch.object( + shadow_campaign_cli, + "iter_shadow_campaign_events", + side_effect=AssertionError("event zero reached"), + ), + ): + status, payload = _invoke( + [ + "run", + "--repository-sha", + "1" * 40, + "--postgres-database", + "secure_eval_phase8b_shadow_campaign_cli_test", + ] + ) + self.assertEqual(status, 2) + self.assertEqual(payload["failure_stage"], "target_or_runtime") + self.assertEqual(payload["blockers"], ["campaign_operation_failed_closed"]) + connect.assert_not_called() + + def test_valid_connection_delegates_authentication_to_libpq(self): + connection = _Connection() + connect = Mock(return_value=connection) + args = SimpleNamespace( + postgres_database="secure_eval_phase8b_shadow_campaign_cli_test", + postgres_host="::1", + postgres_port=5432, + postgres_user="synthetic_campaign_user", + postgres_sslmode="disable", + ) + with patch.dict(sys.modules, {"psycopg": SimpleNamespace(connect=connect)}): + database, host, observed = shadow_campaign_cli._connect(args) + self.assertEqual(database, args.postgres_database) + self.assertEqual(host, "::1") + self.assertIs(observed, connection) + self.assertNotIn("password", connect.call_args.kwargs) + self.assertEqual(connect.call_args.kwargs["dbname"], database) + + def test_persistent_commands_use_one_repository_and_fixed_safe_output(self): + for command in ("run", "resume", "inspect", "verify"): + with self.subTest(command=command): + connection = _Connection() + runtime = Mock() + runtime.run.return_value = _result() + runtime.resume.return_value = _result() + runtime.inspect.return_value = _progress() + runtime.verify.return_value = _progress() + arguments = [ + command, + "--campaign", + "full-90d", + "--repository-sha", + REPOSITORY_SHA, + "--postgres-host", + "127.0.0.1", + "--postgres-database", + "secure_eval_phase8b_shadow_campaign_cli_test", + ] + with ( + patch.object( + shadow_campaign_cli, + "_connect", + return_value=( + "secure_eval_phase8b_shadow_campaign_cli_test", + "127.0.0.1", + connection, + ), + ), + patch.object( + shadow_campaign_cli, + "resolve_runtime_repository_identity", + return_value=SimpleNamespace(observed_commit_sha=REPOSITORY_SHA), + ), + patch.object(shadow_campaign_cli, "PostgresShadowRepository"), + patch.object( + shadow_campaign_cli, + "ShadowCampaignRuntime", + return_value=runtime, + ), + ): + status, payload = _invoke(arguments) + self.assertEqual(status, 0) + self.assertTrue(connection.closed) + self.assertEqual(payload["operation"], "phase8b_historical_shadow_campaign") + self.assertEqual( + tuple(payload["segment_decision_counts"]), SEGMENT_IDS, + ) + self.assertEqual(tuple(payload["segment_result_hashes"]), SEGMENT_IDS) + self.assertEqual(tuple(payload["window_result_hashes"]), WINDOW_LABELS) + self.assertEqual(payload["network_write_count"], 0) + self.assertEqual(payload["credential_read_count"], 0) + self.assertIs(payload["production_submit_reachable"], False) + self.assertNotIn("postgres_user", payload) + if command == "verify": + runtime.verify.assert_called_once_with() + runtime.inspect.assert_not_called() + elif command == "inspect": + runtime.inspect.assert_called_once_with() + runtime.verify.assert_not_called() + + def test_runtime_failure_discards_exception_stack_path_and_database_details(self): + connection = _Connection() + with ( + patch.object( + shadow_campaign_cli, + "_connect", + return_value=( + "secure_eval_phase8b_shadow_campaign_cli_test", + "127.0.0.1", + connection, + ), + ), + patch.object( + shadow_campaign_cli, + "resolve_runtime_repository_identity", + return_value=SimpleNamespace(observed_commit_sha=REPOSITORY_SHA), + ), + patch.object( + shadow_campaign_cli, + "PostgresShadowRepository", + side_effect=RuntimeError("C:\\private\\password=secret database detail"), + ), + ): + status, payload = _invoke( + [ + "inspect", + "--repository-sha", + REPOSITORY_SHA, + "--postgres-database", + "secure_eval_phase8b_shadow_campaign_cli_test", + ] + ) + encoded = json.dumps(payload) + self.assertEqual(status, 2) + self.assertNotIn("private", encoded.lower()) + self.assertNotIn("password", encoded.lower()) + self.assertNotIn("secure_eval_phase8b_shadow_campaign_cli_test", encoded) + self.assertTrue(connection.closed) + + def test_structured_runtime_failure_preserves_truthful_public_counters(self): + connection = _Connection() + runtime = Mock() + runtime.run.side_effect = ShadowCampaignInjectedCrash( + "after_checkpoint_calculation", + { + "processed_event_count": 17, + "committed_row_count": 17, + "first_missing_index": 17, + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + }, + ) + with ( + patch.object( + shadow_campaign_cli, + "_connect", + return_value=( + "secure_eval_phase8b_shadow_campaign_cli_test", + "127.0.0.1", + connection, + ), + ), + patch.object( + shadow_campaign_cli, + "resolve_runtime_repository_identity", + return_value=SimpleNamespace(observed_commit_sha=REPOSITORY_SHA), + ), + patch.object(shadow_campaign_cli, "PostgresShadowRepository"), + patch.object( + shadow_campaign_cli, + "ShadowCampaignRuntime", + return_value=runtime, + ), + ): + status, payload = _invoke( + [ + "run", + "--repository-sha", + REPOSITORY_SHA, + "--postgres-database", + "secure_eval_phase8b_shadow_campaign_cli_test", + ] + ) + self.assertEqual(status, 2) + self.assertEqual(payload["processed_event_count"], 17) + self.assertEqual(payload["committed_row_count"], 17) + self.assertEqual(payload["first_missing_index"], 17) + self.assertEqual(payload["failure_stage"], "campaign_runtime") + self.assertEqual(payload["blockers"], ["campaign_runtime_interrupted"]) + self.assertEqual(payload["network_write_count"], 0) + self.assertTrue(connection.closed) + + def test_operation_failure_preserves_exact_runtime_stage_and_progress(self): + progress = { + "processed_event_count": 17, + "committed_row_count": 16, + "first_missing_index": 16, + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + } + for stage in ( + "repository_validation", + "event_execution", + "final_repository_validation", + ): + with self.subTest(stage=stage): + connection = _Connection() + runtime = Mock() + runtime.run.side_effect = ShadowCampaignOperationFailure( + dict(progress), failure_stage=stage + ) + with ( + patch.object( + shadow_campaign_cli, + "_connect", + return_value=( + "secure_eval_phase8b_shadow_campaign_cli_test", + "127.0.0.1", + connection, + ), + ), + patch.object( + shadow_campaign_cli, + "resolve_runtime_repository_identity", + return_value=SimpleNamespace( + observed_commit_sha=REPOSITORY_SHA + ), + ), + patch.object(shadow_campaign_cli, "PostgresShadowRepository"), + patch.object( + shadow_campaign_cli, + "ShadowCampaignRuntime", + return_value=runtime, + ), + ): + status, payload = _invoke([ + "run", + "--repository-sha", + REPOSITORY_SHA, + "--postgres-database", + "secure_eval_phase8b_shadow_campaign_cli_test", + ]) + self.assertEqual(status, 2) + self.assertEqual(payload["processed_event_count"], 17) + self.assertEqual(payload["committed_row_count"], 16) + self.assertEqual(payload["first_missing_index"], 16) + self.assertEqual(payload["failure_stage"], stage) + self.assertEqual( + payload["blockers"], ["campaign_operation_failed_closed"] + ) + self.assertEqual(payload["network_write_count"], 0) + self.assertTrue(connection.closed) + + def test_authority_conflict_preserves_nonzero_operation_progress(self): + connection = _Connection() + runtime = Mock() + runtime.run.side_effect = ShadowCampaignSpecConflict( + "public-safe authority conflict", + progress={ + "processed_event_count": 6, + "committed_row_count": 6, + "first_missing_index": 6, + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + }, + failure_stage="event_execution", + ) + with ( + patch.object( + shadow_campaign_cli, + "_connect", + return_value=( + "secure_eval_phase8b_shadow_campaign_cli_test", + "127.0.0.1", + connection, + ), + ), + patch.object( + shadow_campaign_cli, + "resolve_runtime_repository_identity", + return_value=SimpleNamespace(observed_commit_sha=REPOSITORY_SHA), + ), + patch.object(shadow_campaign_cli, "PostgresShadowRepository"), + patch.object( + shadow_campaign_cli, "ShadowCampaignRuntime", return_value=runtime + ), + ): + status, payload = _invoke([ + "run", "--repository-sha", REPOSITORY_SHA, + "--postgres-database", + "secure_eval_phase8b_shadow_campaign_cli_test", + ]) + self.assertEqual(status, 2) + self.assertEqual( + (payload["processed_event_count"], payload["committed_row_count"], + payload["first_missing_index"]), + (6, 6, 6), + ) + self.assertEqual(payload["failure_stage"], "event_execution") + self.assertEqual(payload["blockers"], ["campaign_authority_conflict"]) + self.assertTrue(connection.closed) + + def test_unsealed_mapping_proxy_is_rejected_before_nested_callback(self): + class EvilDict(dict): + callbacks = 0 + + def get(self, *args, **kwargs): + EvilDict.callbacks += 1 + raise AssertionError("forbidden unsealed progress lookup executed") + + progress = EvilDict({ + "processed_event_count": 6, + "committed_row_count": 6, + "first_missing_index": 6, + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + }) + with self.assertRaises(TypeError): + shadow_campaign_cli._failure_payload_from_progress( + "run", MappingProxyType(progress) + ) + self.assertEqual(EvilDict.callbacks, 0) + + def test_public_output_allowlist_fails_closed_on_unsafe_nested_result(self): + result = _result() + result.blocker_frequencies = {"C:\\private\\raw-account.json": 1} + output = io.StringIO() + with patch("sys.stdout", output): + emitted = shadow_campaign_cli._emit( + shadow_campaign_cli._result_payload("run", result) + ) + payload = json.loads(output.getvalue()) + self.assertIs(emitted, False) + self.assertEqual(payload["status"], "blocked") + self.assertEqual(payload["processed_event_count"], 25_920) + self.assertEqual(payload["committed_row_count"], 25_920) + self.assertIsNone(payload["first_missing_index"]) + self.assertNotIn("private", output.getvalue().lower()) + self.assertNotIn("raw-account", output.getvalue().lower()) + + def test_recursive_schema_rejects_nested_extras_bool_integers_and_bad_vocab(self): + mutations = [] + + extra = shadow_campaign_cli._result_payload("run", _result()) + extra["segment_decision_counts"][SEGMENT_IDS[0]]["benign_note"] = 1 + mutations.append(("extra nested key", extra)) + + bool_count = shadow_campaign_cli._result_payload("run", _result()) + bool_count["segment_decision_counts"][SEGMENT_IDS[0]]["accepted"] = True + mutations.append(("bool as integer", bool_count)) + + unreviewed = shadow_campaign_cli._result_payload("run", _result()) + unreviewed["blocker_frequencies"] = {"benign_policy_note": 1} + mutations.append(("unreviewed blocker", unreviewed)) + + malformed_hash = shadow_campaign_cli._result_payload("run", _result()) + malformed_hash["segment_result_hashes"][SEGMENT_IDS[0]] = "B" * 64 + mutations.append(("malformed hash", malformed_hash)) + + wrong_order = shadow_campaign_cli._result_payload("run", _result()) + wrong_order["window_result_hashes"] = { + "7d": "c" * 64, + "1d": "c" * 64, + "30d": "c" * 64, + "90d": "c" * 64, + } + mutations.append(("wrong nested order", wrong_order)) + + for name, payload in mutations: + with self.subTest(name=name): + emitted, rejected = _emit_direct(payload) + self.assertIs(emitted, False) + self.assertEqual(rejected["status"], "blocked") + self.assertEqual( + rejected["blockers"], + ["campaign_result_serialization_failed"], + ) + + def test_failure_schema_rejects_bool_counter_and_unreviewed_stage(self): + bool_counter = shadow_campaign_cli._failure_payload( + "run", + "campaign_operation_failed_closed", + failure_stage="target_or_runtime", + ) + bool_counter["processed_event_count"] = True + unreviewed_stage = shadow_campaign_cli._failure_payload( + "run", + "campaign_operation_failed_closed", + failure_stage="benign_stage", + ) + for payload in (bool_counter, unreviewed_stage): + emitted, rejected = _emit_direct(payload) + self.assertIs(emitted, False) + self.assertEqual(rejected["failure_stage"], "serialization") + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_concurrency.py b/open-core/tests/test_phase8b_shadow_campaign_concurrency.py new file mode 100644 index 0000000..84032e6 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_concurrency.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import unittest + +from secure_eval_wrapper.data_collection.hashing import sha256_payload +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + _run_concurrency_matrix, +) +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + + +EXPECTED_CASES = ( + "two_identical_campaign_starts", + "run_and_resume", + "two_resumes", + "different_event_partitions", + "same_campaign_id_different_spec", + "different_campaign_ids_same_spec_shape", + "inspect_reader_with_writer", + "verifier_with_writer", + "corrupted_scan_with_new_writer", + "post_commit_response_loss_with_retry", +) + + +class Phase8BShadowCampaignConcurrencyTests(unittest.TestCase): + def test_all_ten_concurrency_cases_execute_with_explicit_semantics(self): + results = _run_concurrency_matrix(TEST_REPOSITORY_SHA) + self.assertEqual(len(results), 10) + self.assertEqual(tuple(result["case"] for result in results), EXPECTED_CASES) + self.assertEqual(len({result["result_sha256"] for result in results}), 10) + for result in results: + with self.subTest(case=result["case"]): + self.assertIs(result["passed"], True) + self.assertTrue(result["checks"]) + self.assertTrue(all(result["checks"].values())) + core = { + key: value + for key, value in result.items() + if key != "result_sha256" + } + self.assertEqual(result["result_sha256"], sha256_payload(core)) + + def test_matrix_binds_required_conflict_and_isolation_cases(self): + by_case = { + result["case"]: result + for result in _run_concurrency_matrix(TEST_REPOSITORY_SHA) + } + self.assertTrue( + by_case["same_campaign_id_different_spec"]["checks"][ + "explicit_conflict" + ] + ) + self.assertTrue( + by_case["different_campaign_ids_same_spec_shape"]["checks"][ + "authority_sets_disjoint" + ] + ) + self.assertTrue( + by_case["corrupted_scan_with_new_writer"]["checks"][ + "new_writer_rejected" + ] + ) + self.assertTrue( + by_case["corrupted_scan_with_new_writer"]["checks"]["no_overwrite"] + ) + self.assertTrue( + by_case["post_commit_response_loss_with_retry"]["checks"][ + "single_authoritative_row" + ] + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_corpus.py b/open-core/tests/test_phase8b_shadow_campaign_corpus.py new file mode 100644 index 0000000..d0969d6 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_corpus.py @@ -0,0 +1,394 @@ +from __future__ import annotations + +import os +import socket +import unittest +from dataclasses import FrozenInstanceError, replace +from datetime import timedelta +from itertools import islice +from unittest.mock import patch + +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + SYNTHETIC_ACCOUNT_TIMELINE_STATES, + scenario_account_payload, + synthetic_account_snapshot_for_event, + synthetic_account_timeline_sha256, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + COMPACT_CAMPAIGN_MINIMUM_EVENT_COUNT, + FULL_90D_CAMPAIGN_SPEC, + FULL_90D_CAMPAIGN_SPEC_SHA256, + SHADOW_CAMPAIGN_GENERATOR_SHA256, + calculate_shadow_campaign_corpus_hashes, + full_90d_campaign_spec, + iter_compact_shadow_campaign_events, + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import ShadowCampaignEvent +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + campaign_scenario_for_event, +) + + +RUN_COMPACT = os.getenv("RUN_SHADOW_CAMPAIGN_COMPACT", "").lower() == "true" +RUN_FULL = os.getenv("RUN_SHADOW_CAMPAIGN_FULL", "").lower() == "true" + + +EXPECTED_REGIMES = ( + "quiet_range", + "low_volatility_uptrend", + "low_volatility_downtrend", + "high_volatility_uptrend", + "high_volatility_downtrend", + "volatility_spike", + "flash_drawdown_and_bounded_rebound", + "deterministic_gap_sequence", + "illiquid_wide_spread_conditions", + "stale_missing_duplicate_out_of_order_sequence", + "instrument_metadata_boundary_churn", + "recovery_and_normalization", +) + +EXPECTED_SEGMENT_HASHES = { + "full-90d-segment-01-quiet-range": "34312c52eb7b722685376009a7a74d147550cce44ba85e768a323014ab00d0dc", + "full-90d-segment-02-low-volatility-uptrend": "431077f03cc9c31a0e5305bf91f4bd0ec0ee4e871972e9bf6a31034a015ef24a", + "full-90d-segment-03-low-volatility-downtrend": "c72358961f6f434bfbfacb3bf4496f25ac00e5f072ffaad53e151df43e61cc04", + "full-90d-segment-04-high-volatility-uptrend": "16f851680db8310f59a893f441ed10a3820015a53d1c75ba7d20a561b42a52b4", + "full-90d-segment-05-high-volatility-downtrend": "362fdf03af4eee6633f53ce9e0abb65e8a93f85134f5536027db0a00a2d76dc9", + "full-90d-segment-06-volatility-spike": "80349e2c3a5c37a30914607f4fe957ed0a927d0e3e079d2c21317b96569327e8", + "full-90d-segment-07-flash-drawdown-and-bounded-rebound": "219412b4ee724d5d0c93326634f833168871dea1767b88dad8b012f3ca75a81d", + "full-90d-segment-08-deterministic-gap-sequence": "4cc780f8ac577fe9a3acc4736c6a3e195937971ee22385158638b321eac13479", + "full-90d-segment-09-illiquid-wide-spread-conditions": "b8c1763af98d80f9aa777cd97f2993bdde1d2002f07aac7ccd2daccd34c51cef", + "full-90d-segment-10-stale-missing-duplicate-out-of-order-sequence": "ff02d27289b3a7756faa654890cfa0944537b3b7b398911034f50d0e9fcd1770", + "full-90d-segment-11-instrument-metadata-boundary-churn": "f6688ab95cb0e08c32c74738c903f2b74423243215cbc1e0f99b1a3ec226541b", + "full-90d-segment-12-recovery-and-normalization": "8f3155743750afc9532ad60837f92bf29fbd664bc685211c2d12bb6e0abb283c", +} + +EXPECTED_WINDOW_HASHES = { + "1d": "eb3171e97569c582a5dc4e9c39c392f4db8d279032a98a387ba564c4e0747188", + "7d": "baad30f6ecd49f184a2b68b16a09e9edb758933d454ad136590a50aaf1c429cc", + "30d": "ecf548a22c2ddc125afdc00424a9fce4adb3ffd539cdb6eb167d625dc20d008c", + "90d": "7a58d2a2984f8722bc3f13cc7f67a1b04d10e11ae21534d7665d2caf6f8eb9a2", +} + +def _lineage_specs(): + canonical = FULL_90D_CAMPAIGN_SPEC + prefix_segment = replace( + canonical.segments[0], + segment_id="identity-prefix-segment", + event_count=4, + ) + future_segment = replace( + canonical.segments[1], + segment_id="identity-future-a", + start_event_index=4, + event_count=4, + ) + base = replace( + canonical, + campaign_id="phase8b-shadow-identity-a", + campaign_version="identity-lineage-a", + event_count=8, + events_per_segment=4, + equivalent_duration_days=1, + segments=(prefix_segment, future_segment), + window_labels=("prefix-a", "complete-a"), + window_event_counts=(4, 8), + ) + changed_future = replace( + future_segment, + segment_id="identity-future-b", + regime="future-lineage-relabel", + seed=future_segment.seed + 10_003, + ) + variant = replace( + base, + campaign_id="phase8b-shadow-identity-b", + campaign_version="identity-lineage-b", + segments=(prefix_segment, changed_future), + window_labels=("prefix-b", "complete-b"), + ) + return base, variant + + +class Phase8BShadowCampaignCorpusTests(unittest.TestCase): + def test_full_90d_spec_has_exact_scale_windows_and_named_regimes(self): + spec = FULL_90D_CAMPAIGN_SPEC + self.assertEqual(spec, full_90d_campaign_spec()) + self.assertEqual(spec.instrument, "BTC-USDT") + self.assertEqual(spec.instrument_type, "spot") + self.assertEqual(spec.timeframe, "5m") + self.assertEqual(spec.equivalent_duration_days, 90) + self.assertEqual(spec.event_count, 25_920) + self.assertEqual(spec.segment_count, 12) + self.assertEqual(spec.events_per_segment, 2_160) + self.assertEqual(dict(spec.windows), {"1d": 288, "7d": 2_016, "30d": 8_640, "90d": 25_920}) + self.assertEqual(tuple(segment.regime for segment in spec.segments), EXPECTED_REGIMES) + self.assertEqual(tuple(segment.start_index for segment in spec.segments), tuple(range(0, 25_920, 2_160))) + self.assertEqual(len(set(spec.fixed_seeds)), 12) + self.assertTrue(spec.repository_owned) + self.assertFalse(spec.network_access_allowed) + self.assertFalse(spec.credential_access_allowed) + self.assertFalse(spec.user_file_access_allowed) + self.assertEqual( + SHADOW_CAMPAIGN_GENERATOR_SHA256, + "99a930e21c1c0a08e10bc3fe1bab80afdccaf8706de0cb6c2fe3e81a602e4fee", + ) + self.assertEqual( + FULL_90D_CAMPAIGN_SPEC_SHA256, + "2cc12c280c9a2c7bf59f118b77dded82498eb736bfa2a4ddaba8a97349ba9c0f", + ) + + def test_models_are_frozen_and_production_api_is_streaming(self): + with self.assertRaises(FrozenInstanceError): + FULL_90D_CAMPAIGN_SPEC.event_count = 1 + iterator = iter_shadow_campaign_events() + self.assertNotIsInstance(iterator, (tuple, list)) + self.assertIs(iter(iterator), iterator) + event = next(iterator) + self.assertIsInstance(event, ShadowCampaignEvent) + with self.assertRaises(FrozenInstanceError): + event.event_index = 2 + + def test_event_ids_hashes_slices_and_runtime_allowlist_are_stable(self): + first = tuple(islice(iter_shadow_campaign_events(), 32)) + second = tuple(islice(iter_shadow_campaign_events(), 32)) + self.assertEqual( + tuple((event.event_id, event.event_sha256) for event in first), + tuple((event.event_id, event.event_sha256) for event in second), + ) + self.assertEqual(len({event.event_id for event in first}), 32) + sliced = tuple(iter_shadow_campaign_events(start_index=10, stop_index=20)) + self.assertEqual( + tuple(event.event_sha256 for event in sliced), + tuple(event.event_sha256 for event in first[10:20]), + ) + expected_keys = { + "provider", + "instrument", + "instrument_type", + "timeframe", + "bid", + "ask", + "last_price", + "public_timestamp_utc", + "decision_at_utc", + "instrument_status", + "settlement_asset", + "tick_size", + "lot_size", + "minimum_quantity", + "maximum_quantity", + "direction", + "quantity", + "limit_price", + "order_type", + "quality_flags", + } + self.assertEqual(set(first[0].runtime_payload), expected_keys) + self.assertNotIn("regime", first[0].runtime_payload) + self.assertNotIn("expected_account_blockers", first[0].runtime_payload) + + def test_data_identity_and_prefix_hashes_ignore_campaign_lineage(self): + base_spec, variant_spec = _lineage_specs() + self.assertNotEqual(base_spec.spec_sha256, variant_spec.spec_sha256) + + base_events = tuple(iter_shadow_campaign_events(base_spec)) + variant_events = tuple(iter_shadow_campaign_events(variant_spec)) + self.assertEqual( + tuple(event.event_id for event in base_events), + tuple(event.event_id for event in variant_events), + ) + self.assertEqual( + tuple(event.event_sha256 for event in base_events[:4]), + tuple(event.event_sha256 for event in variant_events[:4]), + ) + self.assertNotEqual( + base_events[4].event_sha256, + variant_events[4].event_sha256, + ) + + base_prefix = calculate_shadow_campaign_corpus_hashes( + base_spec, event_limit=4 + ) + variant_prefix = calculate_shadow_campaign_corpus_hashes( + variant_spec, event_limit=4 + ) + self.assertEqual( + base_prefix.event_sequence_sha256, + variant_prefix.event_sequence_sha256, + ) + self.assertEqual( + dict(base_prefix.segment_hashes), + dict(variant_prefix.segment_hashes), + ) + self.assertNotEqual( + base_prefix.campaign_result_sha256, + variant_prefix.campaign_result_sha256, + ) + + event = base_events[0] + relabeled = replace( + event, + campaign_id="unrelated-campaign-lineage", + campaign_spec_sha256="f" * 64, + corpus_generator_version="unrelated-generator-label", + corpus_generator_sha256="e" * 64, + segment_id="unrelated-segment-label", + segment_index=99, + segment_event_index=99, + ) + self.assertEqual(relabeled.event_id, event.event_id) + self.assertEqual(relabeled.event_sha256, event.event_sha256) + self.assertEqual( + set(event.canonical_payload), + { + "operation", + "event_index", + "event_id", + "scheduled_at_utc", + "market_timestamp_utc", + "open_price", + "high_price", + "low_price", + "close_price", + "volume", + "runtime_payload", + }, + ) + with self.assertRaisesRegex(ValueError, "cannot be later"): + replace( + event, + market_timestamp_utc=event.scheduled_at_utc + + timedelta(minutes=5), + ) + def test_market_timestamps_are_historical_and_gaps_remain_unavailable(self): + expected_gap_indexes = { + 7 * FULL_90D_CAMPAIGN_SPEC.events_per_segment + offset + for offset in (360, 720, 1_080, 1_440, 1_800) + } + observed_gap_indexes = set() + for event in iter_shadow_campaign_events(): + self.assertLessEqual( + event.market_timestamp_utc, + event.scheduled_at_utc, + msg=f"future market timestamp at event {event.event_index}", + ) + self.assertLessEqual( + event.runtime_payload["public_timestamp_utc"], + event.runtime_payload["decision_at_utc"], + ) + if "deterministic_source_gap" not in event.quality_flags: + continue + observed_gap_indexes.add(event.event_index) + self.assertEqual( + event.scheduled_at_utc - event.market_timestamp_utc, + timedelta(minutes=15), + ) + scenario = campaign_scenario_for_event( + event, + synthetic_account_snapshot_for_event(event.event_index), + ) + self.assertIs(scenario.market_payload["response_complete"], False) + self.assertEqual( + scenario.market_payload["failure_kind"], + "partial_page", + ) + self.assertEqual(observed_gap_indexes, expected_gap_indexes) + + def test_generator_does_not_use_socket_environment_or_files(self): + with ( + patch("builtins.open", side_effect=AssertionError("file access forbidden")), + patch.object(socket, "socket", side_effect=AssertionError("socket forbidden")), + patch("os.getenv", side_effect=AssertionError("environment forbidden")), + ): + events = tuple(islice(iter_shadow_campaign_events(), 24)) + self.assertEqual(len(events), 24) + + def test_synthetic_timeline_covers_all_states_and_exact_ranges(self): + timeline = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + self.assertEqual(len(timeline), 12) + self.assertEqual(tuple(item.state_name for item in timeline), SYNTHETIC_ACCOUNT_TIMELINE_STATES) + self.assertEqual(len({item.snapshot_id for item in timeline}), 12) + self.assertEqual(len({item.snapshot_sha256 for item in timeline}), 12) + for index, snapshot in enumerate(timeline): + self.assertTrue(snapshot.synthetic_account) + self.assertIs(snapshot.runtime_payload["synthetic_account"], True) + self.assertEqual(snapshot.effective_start_event_index, index * 2_160) + self.assertEqual(snapshot.effective_end_event_index, (index + 1) * 2_160) + self.assertNotIn("expected_account_blockers", snapshot.runtime_payload) + self.assertIs(synthetic_account_snapshot_for_event(index * 2_160), snapshot) + self.assertIs(synthetic_account_snapshot_for_event(25_919), timeline[-1]) + with self.assertRaises(IndexError): + synthetic_account_snapshot_for_event(25_920) + self.assertEqual( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + "6984f7a02e809aa13e2a3c2b733d111aa6a86babaa9786012637db428b6c3a16", + ) + self.assertEqual( + synthetic_account_timeline_sha256(timeline), + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + ) + + def test_expectation_metadata_is_separate_from_runtime_account_hash(self): + original = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + changed = replace(original, expected_account_blockers=("test_only_expected_blocker",)) + self.assertEqual(changed.snapshot_id, original.snapshot_id) + self.assertEqual(changed.runtime_payload_sha256, original.runtime_payload_sha256) + self.assertEqual(changed.snapshot_sha256, original.snapshot_sha256) + self.assertNotEqual(changed.expected_metadata_sha256, original.expected_metadata_sha256) + self.assertNotEqual(changed.timeline_entry_sha256, original.timeline_entry_sha256) + detached = scenario_account_payload(original) + self.assertIsInstance(detached["balances"], list) + detached["balances"][0]["available"] = "0" + self.assertNotEqual(detached["balances"][0]["available"], original.runtime_payload["balances"][0]["available"]) + + @unittest.skipUnless( + RUN_COMPACT, + "set RUN_SHADOW_CAMPAIGN_COMPACT=true for the 1,440-event campaign", + ) + def test_compact_1440_cross_platform_exact_hash(self): + events = iter_compact_shadow_campaign_events( + event_count=COMPACT_CAMPAIGN_MINIMUM_EVENT_COUNT + ) + self.assertNotIsInstance(events, (tuple, list)) + self.assertEqual(sum(1 for _ in events), 1_440) + first = calculate_shadow_campaign_corpus_hashes(event_limit=1_440) + second = calculate_shadow_campaign_corpus_hashes(event_limit=1_440) + self.assertEqual(first.as_dict(), second.as_dict()) + self.assertEqual( + first.event_sequence_sha256, + "c55054bdd7d604273f1dcdc09b6e56a9219cce16a13266d39585632266392c59", + ) + self.assertEqual( + first.campaign_result_sha256, + "466933e0cff98515fa94042b94c254e778630a9457b9b2986dd250cb0e2a3b96", + ) + self.assertEqual(dict(first.window_hashes), {"1d": EXPECTED_WINDOW_HASHES["1d"]}) + self.assertEqual(dict(first.segment_hashes), {}) + self.assertFalse(first.complete) + + @unittest.skipUnless( + RUN_FULL, + "set RUN_SHADOW_CAMPAIGN_FULL=true for the 25,920-event campaign", + ) + def test_full_25920_segment_window_and_campaign_hashes(self): + result = calculate_shadow_campaign_corpus_hashes() + self.assertEqual(result.event_count, 25_920) + self.assertTrue(result.complete) + self.assertEqual( + result.event_sequence_sha256, + "7a58d2a2984f8722bc3f13cc7f67a1b04d10e11ae21534d7665d2caf6f8eb9a2", + ) + self.assertEqual(dict(result.segment_hashes), EXPECTED_SEGMENT_HASHES) + self.assertEqual(dict(result.window_hashes), EXPECTED_WINDOW_HASHES) + self.assertEqual( + result.campaign_result_sha256, + "2f5c235c6cd4dca92d1938791aad73b869bc7183474dcc1937f50fa9e5da1ecc", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_crash.py b/open-core/tests/test_phase8b_shadow_campaign_crash.py new file mode 100644 index 0000000..e20de28 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_crash.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import unittest + +from secure_eval_wrapper.data_collection.hashing import sha256_payload +from secure_eval_wrapper.live.shadow_campaign_runtime import CAMPAIGN_CRASH_POINTS +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + _EXPECTED_CAMPAIGN_CRASH_POINTS, + _EXPECTED_RUNTIME_CRASH_POINTS, + _run_crash_matrices, + passed_campaign_case_count, +) +from secure_eval_wrapper.live.shadow_runtime import RUNTIME_CRASH_POINTS + +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + + +class Phase8BShadowCampaignCrashTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.runtime_results, cls.campaign_results = _run_crash_matrices( + TEST_REPOSITORY_SHA + ) + + def test_all_nine_shared_runtime_crash_points_recover_deterministically(self): + self.assertEqual(tuple(RUNTIME_CRASH_POINTS), _EXPECTED_RUNTIME_CRASH_POINTS) + self.assertEqual(len(self.runtime_results), 9) + payload = {"runtime_crash_results": self.runtime_results} + self.assertEqual( + passed_campaign_case_count(payload, "runtime_crash_results"), 9 + ) + self.assertEqual( + tuple(result["case"] for result in self.runtime_results), + tuple(f"runtime_{point}" for point in RUNTIME_CRASH_POINTS), + ) + + def test_all_ten_campaign_crash_points_recover_deterministically(self): + self.assertEqual( + tuple(CAMPAIGN_CRASH_POINTS), _EXPECTED_CAMPAIGN_CRASH_POINTS + ) + self.assertEqual(len(self.campaign_results), 10) + payload = {"campaign_crash_results": self.campaign_results} + self.assertEqual( + passed_campaign_case_count(payload, "campaign_crash_results"), 10 + ) + self.assertEqual( + tuple(result["case"] for result in self.campaign_results), + tuple(f"campaign_{point}" for point in CAMPAIGN_CRASH_POINTS), + ) + + def test_each_crash_result_is_hash_bound_to_executed_checks(self): + for result in (*self.runtime_results, *self.campaign_results): + with self.subTest(case=result["case"]): + core = { + key: value + for key, value in result.items() + if key != "result_sha256" + } + self.assertEqual(result["result_sha256"], sha256_payload(core)) + self.assertTrue(result["passed"]) + self.assertTrue(result["checks"]) + self.assertTrue(all(result["checks"].values())) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_evidence.py b/open-core/tests/test_phase8b_shadow_campaign_evidence.py new file mode 100644 index 0000000..e1f85b2 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_evidence.py @@ -0,0 +1,817 @@ +from __future__ import annotations + +import importlib.util +import json +import os +import unittest +from copy import deepcopy +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + +from secure_eval_wrapper.data_collection.hashing import sha256_payload +from secure_eval_wrapper.live.shadow_campaign_evidence import ( + CAMPAIGN_PUBLIC_EVIDENCE_KEYS, + _scan_public_value, + build_public_historical_shadow_campaign_evidence, + canonical_public_historical_shadow_campaign_evidence_bytes, + load_public_historical_shadow_campaign_evidence, + validate_public_historical_shadow_campaign_evidence, +) +from secure_eval_wrapper.live.shadow_campaign_expected_hashes import ( + EXPECTED_CORPUS_GENERATOR_SHA256, + EXPECTED_FULL_CAMPAIGN_SPEC_SHA256, + EXPECTED_FULL_CORPUS_RESULT_SHA256, + EXPECTED_FULL_EVENT_SEQUENCE_SHA256, + EXPECTED_FULL_HASH_MANIFEST_SHA256, + EXPECTED_FULL_HASH_MANIFEST_VERSION, + EXPECTED_FULL_SEGMENT_HASHES, + EXPECTED_FULL_WINDOW_HASHES, + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, +) +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + CAMPAIGN_VERIFIER_VERSION, + CASE_CATALOGS, + CASE_RESULT_SCHEMAS, + POSTGRESQL_CAMPAIGN_NOT_EXECUTED, + PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY, + VERIFIER_IMPLEMENTATION_SHA256, + VERIFIER_RESULT_KEYS, + passed_campaign_case_count, + run_historical_shadow_campaign_verifier, + validate_historical_shadow_campaign_verifier_result, +) + + +REPOSITORY_SHA = "b" * 40 +RUN_FULL = os.getenv("RUN_SHADOW_CAMPAIGN_FULL", "").lower() == "true" +CHECKED_EVIDENCE = ( + Path(__file__).resolve().parents[2] + / "docs/evidence/phase8b_historical_shadow_campaign_public.json" +) + + +def _case(case: str) -> dict[str, object]: + fact_keys, check_keys = CASE_RESULT_SCHEMAS[case] + facts: dict[str, object] + if case.startswith("restart_checkpoint_"): + checkpoint = int(case.removeprefix("restart_checkpoint_")) + facts = { + "checkpoint_event_count": checkpoint, + "resumed_suffix_event_count": 25_920 - checkpoint, + "first_missing_index": checkpoint, + "final_campaign_sha256": "1" * 64, + } + elif case in { + "gap_at_beginning", "gap_in_middle", "multiple_gaps", + "later_segment_complete_earlier_missing", + }: + gap_catalog = { + "gap_at_beginning": (0,), + "gap_in_middle": (12_960,), + "multiple_gaps": (288, 8_640, 21_600), + "later_segment_complete_earlier_missing": (2_159,), + } + gaps = gap_catalog[case] + facts = { + "gaps": gaps, + "first_missing_index": min(gaps), + "resumed_from_event_index": min(gaps), + } + elif case == "exact_full_replay": + facts = {"event_count": 25_920, "campaign_result_sha256": "1" * 64} + elif case in CASE_CATALOGS["mutation_results"]: + facts = { + "mutation_index": 20, + "parent_campaign_sha256": "1" * 64, + "parent_policy_chain_sha256": "e" * 64, + "mutated_policy_chain_sha256": sha256_payload({"mutation": case}), + "mutation_lineage_sha256": sha256_payload({"lineage": case}), + } + elif case == "future_last_event": + facts = {"prefix_event_count": 25_919} + elif case == "next_segment": + facts = {"current_segment_event_count": 2_160} + elif case == "truncation_prefix": + facts = {"event_count": 777} + elif case == "future_account_snapshot": + facts = {"historical_event_count": 2_160} + elif case == "expected_blocker_metadata": + facts = {"runtime_snapshot_sha256": "8" * 64} + elif case == "canonical_generation_order": + facts = {"canonical_event_count": 288} + elif case.startswith("runtime_"): + point = case.removeprefix("runtime_") + facts = { + "crash_point": point, + "lifecycle_ordinal": CASE_CATALOGS["runtime_crash_results"].index(case), + "committed_after_crash": 0, + } + elif case.startswith("campaign_"): + point = case.removeprefix("campaign_") + facts = { + "crash_point": point, + "lifecycle_ordinal": CASE_CATALOGS["campaign_crash_results"].index(case), + "failure_progress_sha256": "9" * 64, + "committed_after_crash": 0, + } + else: + facts = {} + if tuple(facts) != fact_keys: + raise AssertionError(f"test fixture facts differ for {case}") + core: dict[str, object] = { + "case": case, + **facts, + "checks": {name: True for name in check_keys}, + "passed": True, + } + return {**core, "result_sha256": sha256_payload(core)} + + +def _cases(key: str) -> tuple[dict[str, object], ...]: + return tuple(_case(case) for case in CASE_CATALOGS[key]) + + +def _verifier_result() -> dict[str, object]: + case_results = { + key: _cases(key) + for key in CASE_CATALOGS + } + segment_ids = tuple(EXPECTED_FULL_SEGMENT_HASHES) + segment_runtime_hashes = { + segment_id: sha256_payload({"runtime_segment": segment_id}) + for segment_id in segment_ids + } + segment_decision_counts = { + segment_id: {"accepted": 1_000, "blocked": 1_160} + for segment_id in segment_ids + } + values: dict[str, object] = { + "schema_version": 1, + "operation": "phase8b_historical_shadow_campaign_verifier", + "status": "passed", + "repository_sha": REPOSITORY_SHA, + "campaign_version": "phase8b-historical-shadow-campaign-v1", + "verifier_version": CAMPAIGN_VERIFIER_VERSION, + "verifier_implementation_sha256": VERIFIER_IMPLEMENTATION_SHA256, + "expected_hash_manifest_version": EXPECTED_FULL_HASH_MANIFEST_VERSION, + "expected_hash_manifest_sha256": EXPECTED_FULL_HASH_MANIFEST_SHA256, + "corpus_generator_version": ( + "phase8b-historical-shadow-corpus-generator-v1" + ), + "corpus_spec_sha256": EXPECTED_FULL_CAMPAIGN_SPEC_SHA256, + "corpus_generator_sha256": ( + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256 + ), + "corpus_generator_descriptor_sha256": ( + EXPECTED_CORPUS_GENERATOR_SHA256 + ), + "generator_implementation_sha256": ( + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256 + ), + "synthetic_account_timeline_sha256": ( + EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256 + ), + "timeframe": "5m", + "equivalent_duration_days": 90, + "event_count": 25_920, + "segment_count": 12, + "event_sequence_sha256": EXPECTED_FULL_EVENT_SEQUENCE_SHA256, + "corpus_campaign_result_sha256": EXPECTED_FULL_CORPUS_RESULT_SHA256, + "corpus_segment_result_hashes": dict(EXPECTED_FULL_SEGMENT_HASHES), + "corpus_window_result_hashes": dict(EXPECTED_FULL_WINDOW_HASHES), + "segment_result_hashes": segment_runtime_hashes, + "window_result_hashes": { + "1d": "a" * 64, + "7d": "b" * 64, + "30d": "c" * 64, + "90d": "d" * 64, + }, + "accepted_decision_count": 12_000, + "blocked_decision_count": 13_920, + "segment_decision_counts": segment_decision_counts, + "shadow_intent_count": 12_000, + "blocker_frequencies": {"kill_switch_not_armed": 13_920}, + "final_decision_chain_sha256": "7" * 64, + "campaign_result_sha256": "1" * 64, + "restart_results": case_results["restart_results"], + "restart_result_hashes": tuple( + value["result_sha256"] + for value in case_results["restart_results"] + ), + "replay_results": case_results["replay_results"], + "replay_result_hashes": tuple( + value["result_sha256"] + for value in case_results["replay_results"] + ), + "mutation_results": case_results["mutation_results"], + "mutation_result_hashes": tuple( + value["result_sha256"] + for value in case_results["mutation_results"] + ), + "anti_lookahead_results": case_results["anti_lookahead_results"], + "anti_lookahead_result_hashes": tuple( + value["result_sha256"] + for value in case_results["anti_lookahead_results"] + ), + "concurrency_results": case_results["concurrency_results"], + "concurrency_result_hashes": tuple( + value["result_sha256"] + for value in case_results["concurrency_results"] + ), + "runtime_crash_results": case_results["runtime_crash_results"], + "runtime_crash_result_hashes": tuple( + value["result_sha256"] + for value in case_results["runtime_crash_results"] + ), + "campaign_crash_results": case_results["campaign_crash_results"], + "campaign_crash_result_hashes": tuple( + value["result_sha256"] + for value in case_results["campaign_crash_results"] + ), + "corrupted_bundle_rejections": 1, + "zero_write_facts": { + "network_read_count": 0, + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": False, + "production_cancel_reachable": False, + "real_account_data_used": False, + "operator_database_accessed": False, + }, + "postgresql_campaign": { + "classification": POSTGRESQL_CAMPAIGN_NOT_EXECUTED, + "event_count": 0, + "authoritative_storage_target": "PostgreSQL_16", + }, + } + core = { + key: values[key] + for key in VERIFIER_RESULT_KEYS + if key != "verifier_result_sha256" + } + return {**core, "verifier_result_sha256": sha256_payload(core)} + + +def _rehash_evidence(payload: dict[str, object]) -> None: + core = { + key: payload[key] + for key in CAMPAIGN_PUBLIC_EVIDENCE_KEYS + if key != "evidence_payload_sha256" + } + payload["evidence_payload_sha256"] = sha256_payload(core) + + +def _rehash_verifier(payload: dict[str, object]) -> None: + core = { + key: payload[key] + for key in VERIFIER_RESULT_KEYS + if key != "verifier_result_sha256" + } + payload["verifier_result_sha256"] = sha256_payload(core) + + +class Phase8BShadowCampaignEvidenceTests(unittest.TestCase): + def test_allowlist_counts_hash_and_truthful_postgresql_classification(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ) as executable: + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + + self.assertEqual(executable.call_count, 1) + self.assertEqual(tuple(payload), CAMPAIGN_PUBLIC_EVIDENCE_KEYS) + self.assertEqual(payload["event_count"], 25_920) + self.assertEqual(payload["segment_count"], 12) + self.assertEqual(payload["restart_cases_passed"], 14) + self.assertEqual(payload["replay_cases_passed"], 1) + self.assertEqual(payload["mutation_cases_passed"], 8) + self.assertEqual(payload["anti_lookahead_cases_passed"], 6) + self.assertEqual(payload["concurrency_cases_passed"], 10) + self.assertEqual(payload["runtime_crash_cases_passed"], 9) + self.assertEqual(payload["campaign_crash_cases_passed"], 10) + self.assertEqual( + payload["postgresql_campaign_classification"], + POSTGRESQL_CAMPAIGN_NOT_EXECUTED, + ) + self.assertEqual(payload["postgresql_campaign_event_count"], 0) + self.assertEqual(payload["real_proof_authorization"], "NO") + self.assertEqual(payload["independent_audit_status"], "pending") + expected = { + key: payload[key] + for key in CAMPAIGN_PUBLIC_EVIDENCE_KEYS + if key != "evidence_payload_sha256" + } + self.assertEqual(payload["evidence_payload_sha256"], sha256_payload(expected)) + + def test_three_generations_are_byte_identical_with_ordered_keys(self): + verifier = _verifier_result() + payloads: list[dict[str, object]] = [] + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + side_effect=lambda _sha: deepcopy(verifier), + ): + for _ in range(3): + payloads.append( + build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + ) + encoded = tuple( + json.dumps(payload, separators=(",", ":"), ensure_ascii=True).encode() + for payload in payloads + ) + self.assertEqual(encoded[0], encoded[1]) + self.assertEqual(encoded[1], encoded[2]) + self.assertTrue( + all(tuple(payload) == CAMPAIGN_PUBLIC_EVIDENCE_KEYS for payload in payloads) + ) + self.assertEqual(len({payload["verifier_result_sha256"] for payload in payloads}), 1) + self.assertEqual(len({payload["campaign_result_sha256"] for payload in payloads}), 1) + + def test_artifact_generator_keeps_three_independent_build_calls(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + script_path = ( + Path(__file__).resolve().parents[1] + / "scripts/generate_phase8b_historical_shadow_campaign_evidence.py" + ) + spec = importlib.util.spec_from_file_location( + "phase8b_campaign_evidence_generator_test", + script_path, + ) + self.assertIsNotNone(spec) + self.assertIsNotNone(spec.loader) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + with TemporaryDirectory() as temporary_directory: + output = Path(temporary_directory) / "evidence.json" + with patch.object( + module, + "build_public_historical_shadow_campaign_evidence", + side_effect=lambda **_kwargs: deepcopy(payload), + ) as build, patch.object(module, "_emit"): + result = module.main([ + "--repository-sha", REPOSITORY_SHA, + "--output", str(output), + ]) + self.assertEqual(result, 0) + self.assertEqual(build.call_count, 3) + self.assertEqual(output.read_bytes(), module.canonical_evidence_bytes(payload)) + + def test_artifact_loader_rejects_duplicate_keys_and_noncanonical_bytes(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + canonical = canonical_public_historical_shadow_campaign_evidence_bytes(payload) + self.assertTrue(canonical.endswith(b"\n")) + self.assertFalse(canonical.endswith(b"\n\n")) + self.assertNotIn(b"\r\n", canonical) + for nonfinite in (float("nan"), float("inf"), float("-inf")): + with self.subTest(nonfinite=nonfinite), self.assertRaises(ValueError): + canonical_public_historical_shadow_campaign_evidence_bytes( + {"value": nonfinite} + ) + + class EvilList(list): + iterations = 0 + + def __iter__(self): + EvilList.iterations += 1 + raise AssertionError("forbidden nested list iteration executed") + + class EvilDict(dict): + traversals = 0 + + def items(self): + EvilDict.traversals += 1 + raise AssertionError("forbidden nested mapping traversal executed") + + class EvilText(str): + conversions = 0 + + def encode(self, *args, **kwargs): + EvilText.conversions += 1 + raise AssertionError("forbidden nested scalar conversion executed") + + for hostile in (EvilList([1]), EvilDict({"safe": "value"}), EvilText("value")): + with self.subTest(hostile_type=type(hostile).__name__), self.assertRaises(TypeError): + canonical_public_historical_shadow_campaign_evidence_bytes( + {"value": hostile} + ) + self.assertEqual(EvilList.iterations, 0) + self.assertEqual(EvilDict.traversals, 0) + self.assertEqual(EvilText.conversions, 0) + + attributes = Path(__file__).resolve().parents[2] / ".gitattributes" + self.assertIn( + "docs/evidence/phase8b_historical_shadow_campaign_public.json text eol=lf", + attributes.read_text(encoding="utf-8").splitlines(), + ) + class EvilPathLike: + conversions = 0 + + def __fspath__(self): + EvilPathLike.conversions += 1 + raise AssertionError("forbidden evidence path conversion executed") + + with self.assertRaises(TypeError): + load_public_historical_shadow_campaign_evidence(EvilPathLike()) + self.assertEqual(EvilPathLike.conversions, 0) + + with TemporaryDirectory() as temporary_directory: + path = Path(temporary_directory) / "evidence.json" + path.write_bytes(canonical) + self.assertEqual( + load_public_historical_shadow_campaign_evidence(path), + payload, + ) + last_wins = ( + b'{"repository_sha":"Authorization: Bearer ' + b'abcdefghijklmnopqrstuvwxyz","repository_sha":"' + + REPOSITORY_SHA.encode("ascii") + + b'"}\n' + ) + self.assertEqual( + json.loads(last_wins)["repository_sha"], + REPOSITORY_SHA, + ) + path.write_bytes(last_wins) + with self.assertRaisesRegex(ValueError, "duplicate key"): + load_public_historical_shadow_campaign_evidence(path) + path.write_bytes(b'{"value":NaN}\n') + with self.assertRaisesRegex(ValueError, "invalid constant"): + load_public_historical_shadow_campaign_evidence(path) + path.write_bytes(json.dumps(payload, ensure_ascii=False).encode("utf-8")) + with self.assertRaisesRegex(ValueError, "not canonical"): + load_public_historical_shadow_campaign_evidence(path) + + def test_validator_rejects_hostile_nested_json_before_traversal_or_hashing(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + + class EvilDict(dict): + traversals = 0 + + def items(self): + EvilDict.traversals += 1 + raise AssertionError("forbidden nested mapping traversal executed") + + payload["segment_result_hashes"] = EvilDict( + payload["segment_result_hashes"] + ) + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ), self.assertRaises(TypeError): + validate_public_historical_shadow_campaign_evidence(payload) + self.assertEqual(EvilDict.traversals, 0) + + def test_verifier_boundaries_reject_subclasses_before_callbacks(self): + verifier = _verifier_result() + + class EvilCatalogKey(str): + hashes = 0 + + def __hash__(self): + EvilCatalogKey.hashes += 1 + raise AssertionError("forbidden catalog-key hash executed") + + with self.assertRaises(TypeError): + passed_campaign_case_count(verifier, EvilCatalogKey("restart_results")) + self.assertEqual(EvilCatalogKey.hashes, 0) + + class EvilStoredKey(str): + comparisons = 0 + + __hash__ = str.__hash__ + + def __eq__(self, other): + EvilStoredKey.comparisons += 1 + raise AssertionError("forbidden stored-key comparison executed") + + hostile_key = EvilStoredKey("restart_results") + hostile_key_verifier = {hostile_key: ()} + EvilStoredKey.comparisons = 0 + with self.assertRaises(TypeError): + passed_campaign_case_count(hostile_key_verifier, "restart_results") + self.assertEqual(EvilStoredKey.comparisons, 0) + + class EvilCase(dict): + lookups = 0 + + def get(self, *args, **kwargs): + EvilCase.lookups += 1 + raise AssertionError("forbidden case lookup executed") + + case_verifier = { + "restart_results": (EvilCase(verifier["restart_results"][0]),) + } + with self.assertRaises(TypeError): + passed_campaign_case_count(case_verifier, "restart_results") + self.assertEqual(EvilCase.lookups, 0) + + class EvilTuple(tuple): + iterations = 0 + + def __iter__(self): + EvilTuple.iterations += 1 + raise AssertionError("forbidden verifier tuple iteration executed") + + hostile_verifier = deepcopy(verifier) + hostile_verifier["restart_results"] = EvilTuple( + hostile_verifier["restart_results"] + ) + with self.assertRaises(TypeError): + validate_historical_shadow_campaign_verifier_result( + hostile_verifier, + repository_sha=REPOSITORY_SHA, + ) + self.assertEqual(EvilTuple.iterations, 0) + + class EvilSha(str): + comparisons = 0 + + def __ne__(self, other): + EvilSha.comparisons += 1 + raise AssertionError("forbidden SHA comparison executed") + + with self.assertRaises(TypeError): + validate_historical_shadow_campaign_verifier_result( + verifier, + repository_sha=EvilSha(REPOSITORY_SHA), + ) + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier" + ) as executable, self.assertRaises(TypeError): + build_public_historical_shadow_campaign_evidence( + repository_sha=EvilSha(REPOSITORY_SHA) + ) + executable.assert_not_called() + self.assertEqual(EvilSha.comparisons, 0) + + def test_pass_counts_and_case_hashes_cannot_be_caller_fed(self): + verifier = _verifier_result() + self.assertEqual(passed_campaign_case_count(verifier, "restart_results"), 14) + + stale = deepcopy(verifier) + stale["restart_results"][0]["checks"]["executed"] = False + with self.assertRaises(ValueError): + passed_campaign_case_count(stale, "restart_results") + + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + payload["restart_cases_passed"] = 999 + _rehash_evidence(payload) + with self.assertRaises(ValueError): + validate_public_historical_shadow_campaign_evidence( + payload, verifier_result=verifier + ) + + def test_self_hashed_unknown_fact_and_check_catalogs_are_rejected(self): + for field in ("fact", "check"): + with self.subTest(field=field): + verifier = deepcopy(_verifier_result()) + result = verifier["restart_results"][0] + if field == "fact": + fact_keys, _ = CASE_RESULT_SCHEMAS[result["case"]] + case_core = { + "case": result["case"], + **{name: result[name] for name in fact_keys}, + "unexpected_account_fact": 0, + "checks": result["checks"], + "passed": True, + } + else: + case_core = { + key: value + for key, value in result.items() + if key != "result_sha256" + } + case_core["checks"] = { + **case_core["checks"], + "caller_fed_success": True, + } + forged = { + **case_core, + "result_sha256": sha256_payload(case_core), + } + values = list(verifier["restart_results"]) + values[0] = forged + verifier["restart_results"] = tuple(values) + verifier["restart_result_hashes"] = tuple( + value["result_sha256"] + for value in verifier["restart_results"] + ) + _rehash_verifier(verifier) + with self.assertRaises(ValueError): + validate_historical_shadow_campaign_verifier_result( + verifier, + repository_sha=REPOSITORY_SHA, + ) + + def test_recursive_scanner_rejects_account_secret_strategy_and_path_shapes(self): + malicious_keys = ( + "account", "balances", "positions", "pendingOrders", "equity", + "pnl", "trade_log", "apiToken", "privateKey", "strategyParams", + "expectedReturn", "sharpeRatio", + ) + for key in malicious_keys: + with self.subTest(key=key), self.assertRaises(ValueError): + _scan_public_value({"nested": {key: "redacted"}}) + malicious_values = ( + "Authorization: Bearer abcdefghijklmnopqrstuvwxyz", + "api_token=abcdefghijklmnopqrstuvwxyz0123456789", + "C:\\Users\\operator\\private.json", + "/home/operator/secret.json", + "private strategy parameters", + "expected return and strategy edge", + "aaaaaaaa.bbbbbbbb.cccccccc", + "A" * 48, + ) + for value in malicious_values: + with self.subTest(value=value), self.assertRaises(ValueError): + _scan_public_value({"note": value}) + _scan_public_value({ + "blocker_frequencies": { + name: 1 for name in sorted(PUBLIC_CAMPAIGN_BLOCKER_VOCABULARY) + } + }) + + def test_rehashed_wrong_scalar_types_and_blocker_values_are_rejected(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + payload["event_count"] = True + _rehash_evidence(payload) + with self.assertRaises((TypeError, ValueError)): + validate_public_historical_shadow_campaign_evidence(payload) + + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + payload["blocker_frequencies"] = { + "private_strategy_edge": -1, + } + _rehash_evidence(payload) + with self.assertRaises(ValueError): + validate_public_historical_shadow_campaign_evidence(payload) + + def test_false_postgresql_or_authority_claims_are_rejected(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + payload["postgresql_campaign_classification"] = "POSTGRESQL_EXECUTED" + payload["postgresql_campaign_event_count"] = 2_016 + _rehash_evidence(payload) + with self.assertRaises(ValueError): + validate_public_historical_shadow_campaign_evidence( + payload, verifier_result=verifier + ) + + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + payload["production_write_count"] = 1 + _rehash_evidence(payload) + with self.assertRaises((PermissionError, ValueError)): + validate_public_historical_shadow_campaign_evidence( + payload, verifier_result=verifier + ) + + def test_validator_reexecutes_even_when_caller_supplies_expected_result(self): + verifier = _verifier_result() + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payload = build_public_historical_shadow_campaign_evidence( + repository_sha=REPOSITORY_SHA + ) + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=deepcopy(verifier), + ) as executable: + validate_public_historical_shadow_campaign_evidence( + payload, + verifier_result=verifier, + ) + executable.assert_called_once_with(REPOSITORY_SHA) + + @unittest.skipUnless( + RUN_FULL, + "set RUN_SHADOW_CAMPAIGN_FULL=true for three uncached verifier runs", + ) + def test_full_verifier_and_evidence_are_identical_across_three_runs(self): + self.assertTrue( + CHECKED_EVIDENCE.is_file(), + "full verification requires the checked public evidence artifact", + ) + repository_sha = REPOSITORY_SHA + checked_payload = None + verifiers: list[dict[str, object]] = [] + if CHECKED_EVIDENCE.exists(): + checked_payload = load_public_historical_shadow_campaign_evidence( + CHECKED_EVIDENCE + ) + repository_sha = checked_payload["repository_sha"] + + def execute_and_capture(candidate_sha: str): + result = run_historical_shadow_campaign_verifier(candidate_sha) + verifiers.append(result) + return result + + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + side_effect=execute_and_capture, + ) as strict_execution: + validate_public_historical_shadow_campaign_evidence(checked_payload) + strict_execution.assert_called_once_with(repository_sha) + + while len(verifiers) < 3: + verifiers.append( + run_historical_shadow_campaign_verifier(repository_sha) + ) + self.assertEqual(verifiers[0], verifiers[1]) + self.assertEqual(verifiers[1], verifiers[2]) + + payloads: list[dict[str, object]] = [] + for verifier in verifiers: + with patch( + "secure_eval_wrapper.live.shadow_campaign_evidence." + "run_historical_shadow_campaign_verifier", + return_value=verifier, + ): + payloads.append( + build_public_historical_shadow_campaign_evidence( + repository_sha=repository_sha + ) + ) + compact_bytes = tuple( + json.dumps(payload, separators=(",", ":"), ensure_ascii=True).encode() + for payload in payloads + ) + self.assertEqual(compact_bytes[0], compact_bytes[1]) + self.assertEqual(compact_bytes[1], compact_bytes[2]) + if checked_payload is not None: + self.assertEqual(payloads[0], checked_payload) + artifact_bytes = ( + json.dumps(payloads[0], indent=2, ensure_ascii=False) + "\n" + ).encode("utf-8") + self.assertEqual(artifact_bytes, CHECKED_EVIDENCE.read_bytes()) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_manifest.py b/open-core/tests/test_phase8b_shadow_campaign_manifest.py new file mode 100644 index 0000000..3b84a02 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_manifest.py @@ -0,0 +1,198 @@ +from __future__ import annotations + +from collections.abc import Mapping +import unittest +from pathlib import Path + +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + GENERATOR_IMPLEMENTATION_HASH, + FULL_90D_CAMPAIGN_SPEC, + SHADOW_CAMPAIGN_GENERATOR_SHA256, + calculate_shadow_campaign_corpus_hashes, +) +from secure_eval_wrapper.live.shadow_campaign_expected_hashes import ( + EXPECTED_COMPACT_CORPUS_RESULT_SHA256, + EXPECTED_COMPACT_EVENT_COUNT, + EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256, + EXPECTED_FULL_HASH_MANIFEST_SHA256, + EXPECTED_FULL_SEGMENT_HASHES, + EXPECTED_FULL_WINDOW_HASHES, + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + EXPECTED_GENERATOR_SOURCE_SHA256S, + EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + calculate_generator_source_sha256s, + normalized_source_sha256, + validate_expected_corpus_results, + validate_expected_hash_manifest, + validate_generator_source_implementation, +) +from secure_eval_wrapper.live.shadow_campaign_models import ( + campaign_event_id, + synthetic_timeline_snapshot_id, +) + + + +class Phase8BShadowCampaignManifestTests(unittest.TestCase): + def test_committed_manifest_has_exact_required_cardinalities_and_compact_hashes(self): + self.assertEqual(len(EXPECTED_FULL_SEGMENT_HASHES), 12) + self.assertEqual(tuple(EXPECTED_FULL_WINDOW_HASHES), ("1d", "7d", "30d", "90d")) + self.assertEqual(EXPECTED_COMPACT_EVENT_COUNT, 1_440) + self.assertEqual( + EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256, + "c55054bdd7d604273f1dcdc09b6e56a9219cce16a13266d39585632266392c59", + ) + self.assertEqual( + EXPECTED_COMPACT_CORPUS_RESULT_SHA256, + "466933e0cff98515fa94042b94c254e778630a9457b9b2986dd250cb0e2a3b96", + ) + self.assertEqual( + validate_expected_hash_manifest(), EXPECTED_FULL_HASH_MANIFEST_SHA256 + ) + + def test_real_normalized_source_hash_matches_the_committed_implementation(self): + self.assertEqual( + dict(calculate_generator_source_sha256s()), + dict(EXPECTED_GENERATOR_SOURCE_SHA256S), + ) + self.assertEqual( + validate_generator_source_implementation(), + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + ) + self.assertEqual( + GENERATOR_IMPLEMENTATION_HASH, + EXPECTED_GENERATOR_SOURCE_IMPLEMENTATION_SHA256, + ) + self.assertNotEqual( + GENERATOR_IMPLEMENTATION_HASH, SHADOW_CAMPAIGN_GENERATOR_SHA256 + ) + + def test_source_normalization_is_cross_platform_and_mutation_sensitive(self): + lf = b"alpha = 1\nbeta = 2\n" + self.assertEqual(normalized_source_sha256(lf), normalized_source_sha256(lf.replace(b"\n", b"\r\n"))) + self.assertEqual(normalized_source_sha256(lf), normalized_source_sha256(b"\xef\xbb\xbf" + lf)) + self.assertNotEqual(normalized_source_sha256(lf), normalized_source_sha256(lf.replace(b"2", b"3"))) + + def test_compact_execution_matches_committed_hashes(self): + compact = calculate_shadow_campaign_corpus_hashes(event_limit=1_440) + self.assertEqual(compact.event_count, EXPECTED_COMPACT_EVENT_COUNT) + self.assertEqual( + compact.event_sequence_sha256, EXPECTED_COMPACT_EVENT_SEQUENCE_SHA256 + ) + self.assertEqual( + compact.campaign_result_sha256, EXPECTED_COMPACT_CORPUS_RESULT_SHA256 + ) + self.assertEqual(FULL_90D_CAMPAIGN_SPEC.event_count, 25_920) + self.assertEqual( + EXPECTED_SYNTHETIC_ACCOUNT_TIMELINE_SHA256, + "6984f7a02e809aa13e2a3c2b733d111aa6a86babaa9786012637db428b6c3a16", + ) + def test_identity_helpers_reject_subclasses_before_running_overrides(self): + class EvilInt(int): + comparisons = 0 + + def __lt__(self, other): + EvilInt.comparisons += 1 + raise AssertionError("forbidden integer comparison executed") + + with self.assertRaises(ValueError): + campaign_event_id(event_index=EvilInt(1)) + self.assertEqual(EvilInt.comparisons, 0) + + class EvilText(str): + strips = 0 + + def strip(self, *args, **kwargs): + EvilText.strips += 1 + raise AssertionError("forbidden text method executed") + + with self.assertRaises(ValueError): + synthetic_timeline_snapshot_id( + state_name=EvilText("clean"), + effective_start_event_index=0, + effective_end_event_index=1, + runtime_payload={}, + ) + self.assertEqual(EvilText.strips, 0) + + class EvilMapping(Mapping): + iterations = 0 + + def __iter__(self): + EvilMapping.iterations += 1 + raise AssertionError("forbidden mapping iteration executed") + + def __len__(self): + return 1 + + def __getitem__(self, key): + raise AssertionError("forbidden mapping read executed") + + with self.assertRaises(ValueError): + synthetic_timeline_snapshot_id( + state_name="clean", + effective_start_event_index=0, + effective_end_event_index=1, + runtime_payload=EvilMapping(), + ) + self.assertEqual(EvilMapping.iterations, 0) + + def test_expected_hash_helpers_reject_executable_input_objects_unread(self): + class EvilResult: + property_reads = 0 + + @property + def event_count(self): + EvilResult.property_reads += 1 + raise AssertionError("forbidden result property executed") + + with self.assertRaises(TypeError): + validate_expected_corpus_results( + campaign_spec_sha256="a" * 64, + synthetic_account_timeline_sha256="b" * 64, + corpus_generator_sha256="c" * 64, + full_result=EvilResult(), + compact_result=EvilResult(), + ) + self.assertEqual(EvilResult.property_reads, 0) + + class EvilDigest(str): + comparisons = 0 + + def __ne__(self, other): + EvilDigest.comparisons += 1 + raise AssertionError("forbidden digest comparison executed") + + with self.assertRaises(TypeError): + validate_expected_corpus_results( + campaign_spec_sha256=EvilDigest("a" * 64), + synthetic_account_timeline_sha256="b" * 64, + corpus_generator_sha256="c" * 64, + full_result=EvilResult(), + compact_result=EvilResult(), + ) + self.assertEqual(EvilDigest.comparisons, 0) + + class EvilPathLike: + conversions = 0 + + def __fspath__(self): + EvilPathLike.conversions += 1 + raise AssertionError("forbidden path conversion executed") + + with self.assertRaises(TypeError): + calculate_generator_source_sha256s(EvilPathLike()) + self.assertEqual(EvilPathLike.conversions, 0) + + source_directory = ( + Path(__file__).resolve().parents[1] + / "src/secure_eval_wrapper/live" + ) + self.assertEqual( + dict(calculate_generator_source_sha256s(source_directory)), + dict(calculate_generator_source_sha256s()), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_mutation.py b/open-core/tests/test_phase8b_shadow_campaign_mutation.py new file mode 100644 index 0000000..fd755d2 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_mutation.py @@ -0,0 +1,326 @@ +from __future__ import annotations + +import unittest +from datetime import timedelta +from decimal import Decimal + +from secure_eval_wrapper.data_collection.hashing import sha256_payload +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, + scenario_account_payload, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import iter_shadow_campaign_events +from secure_eval_wrapper.live.shadow_campaign_models import ( + SyntheticAccountTimelineSnapshot, + synthetic_timeline_snapshot_id, +) +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + CASE_RESULT_SCHEMAS, + _MUTATION_CASES, + _run_mutation_matrix, + passed_campaign_case_count, +) +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + + +MUTATION_INDEX = 20 +EVENT_COUNT = 48 +PARENT_CAMPAIGN_SHA256 = "f" * 64 + + +def _decision_hash(event, snapshot) -> str: + return sha256_payload( + { + "market_runtime_payload": event.runtime_payload, + "synthetic_account_snapshot_sha256": snapshot.snapshot_sha256, + } + ) + + +def _snapshot_with_payload(snapshot, payload) -> SyntheticAccountTimelineSnapshot: + snapshot_id = synthetic_timeline_snapshot_id( + state_name=snapshot.state_name, + effective_start_event_index=snapshot.effective_start_event_index, + effective_end_event_index=snapshot.effective_end_event_index, + runtime_payload=payload, + ) + return SyntheticAccountTimelineSnapshot( + snapshot_id=snapshot_id, + state_name=snapshot.state_name, + effective_start_event_index=snapshot.effective_start_event_index, + effective_end_event_index=snapshot.effective_end_event_index, + runtime_payload=payload, + expected_account_blockers=snapshot.expected_account_blockers, + ) + + +def _chain(events, base_snapshot, *, event_override=None, account_override=None): + event_override = {} if event_override is None else event_override + chain_hash = sha256_payload({"operation": "campaign-mutation-chain-v1"}) + event_hashes = [] + decision_hashes = [] + chain_hashes = [] + for event in events: + selected_event = event_override.get(event.event_index, event) + selected_account = ( + account_override + if account_override is not None and event.event_index >= MUTATION_INDEX + else base_snapshot + ) + decision_hash = _decision_hash(selected_event, selected_account) + chain_hash = sha256_payload( + { + "prior_chain_sha256": chain_hash, + "event_sha256": selected_event.event_sha256, + "account_snapshot_sha256": selected_account.snapshot_sha256, + "decision_sha256": decision_hash, + } + ) + event_hashes.append(selected_event.event_sha256) + decision_hashes.append(decision_hash) + chain_hashes.append(chain_hash) + return tuple(event_hashes), tuple(decision_hashes), tuple(chain_hashes) + + +class Phase8BShadowCampaignMutationTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.events = tuple(iter_shadow_campaign_events(stop_index=EVENT_COUNT)) + cls.base_snapshot = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + cls.base_result = _chain(cls.events, cls.base_snapshot) + + def _assert_event_mutation(self, case_name, mutated_event): + original_event = self.events[MUTATION_INDEX] + self.assertEqual(mutated_event.event_id, original_event.event_id) + self.assertNotEqual(mutated_event.event_sha256, original_event.event_sha256) + mutated = _chain( + self.events, + self.base_snapshot, + event_override={MUTATION_INDEX: mutated_event}, + ) + self._assert_prefix_and_suffix(case_name, mutated) + self.assertEqual( + self.events[MUTATION_INDEX].event_sha256, + original_event.event_sha256, + ) + return mutated + + def _assert_account_mutation(self, case_name, mutated_snapshot): + self.assertEqual( + mutated_snapshot.expected_account_blockers, + self.base_snapshot.expected_account_blockers, + ) + self.assertNotEqual( + mutated_snapshot.snapshot_sha256, + self.base_snapshot.snapshot_sha256, + ) + mutated = _chain( + self.events, + self.base_snapshot, + account_override=mutated_snapshot, + ) + self._assert_prefix_and_suffix(case_name, mutated) + return mutated + + def _assert_prefix_and_suffix(self, case_name, mutated): + base_events, base_decisions, base_chain = self.base_result + mutated_events, mutated_decisions, mutated_chain = mutated + self.assertEqual( + mutated_events[:MUTATION_INDEX], base_events[:MUTATION_INDEX] + ) + self.assertEqual( + mutated_decisions[:MUTATION_INDEX], + base_decisions[:MUTATION_INDEX], + ) + self.assertEqual( + mutated_chain[:MUTATION_INDEX], base_chain[:MUTATION_INDEX] + ) + self.assertNotEqual( + mutated_decisions[MUTATION_INDEX], + base_decisions[MUTATION_INDEX], + ) + self.assertTrue( + all( + left != right + for left, right in zip( + mutated_chain[MUTATION_INDEX:], + base_chain[MUTATION_INDEX:], + ) + ) + ) + + logical_run_id = sha256_payload( + { + "campaign_id": "mutation-conflict-test", + "event_id": self.events[MUTATION_INDEX].event_id, + } + ) + authority = {logical_run_id: base_decisions[MUTATION_INDEX]} + classification = ( + "replay" + if authority[logical_run_id] == mutated_decisions[MUTATION_INDEX] + else "conflict" + ) + self.assertEqual(classification, "conflict") + self.assertEqual( + authority[logical_run_id], base_decisions[MUTATION_INDEX] + ) + lineage = { + "mutation_case": case_name, + "parent_campaign_hash": base_chain[-1], + "mutated_campaign_hash": mutated_chain[-1], + } + lineage["campaign_id"] = sha256_payload(lineage) + self.assertEqual(lineage["parent_campaign_hash"], base_chain[-1]) + self.assertNotEqual( + lineage["mutated_campaign_hash"], lineage["parent_campaign_hash"] + ) + return lineage + + def test_price_mutation_preserves_prefix_and_diverges_suffix(self): + original = self.events[MUTATION_INDEX] + new_close = original.close_price + original.tick_size + mutated = original.__class__( + **{ + **{ + field: getattr(original, field) + for field in original.__dataclass_fields__ + }, + "close_price": new_close, + "high_price": max(original.high_price, new_close), + } + ) + self._assert_event_mutation("price", mutated) + + def test_timestamp_mutation_preserves_prefix_and_diverges_suffix(self): + original = self.events[MUTATION_INDEX] + values = { + field: getattr(original, field) for field in original.__dataclass_fields__ + } + values["market_timestamp_utc"] = original.market_timestamp_utc - timedelta( + minutes=5 + ) + mutated = original.__class__(**values) + self.assertLessEqual(mutated.market_timestamp_utc, mutated.scheduled_at_utc) + self._assert_event_mutation("timestamp", mutated) + + def test_spread_mutation_preserves_prefix_and_diverges_suffix(self): + original = self.events[MUTATION_INDEX] + values = { + field: getattr(original, field) for field in original.__dataclass_fields__ + } + values["ask_price"] = original.ask_price + original.tick_size + if original.direction == "long": + values["limit_price"] = values["ask_price"] + self._assert_event_mutation("spread", original.__class__(**values)) + + def test_instrument_metadata_mutation_preserves_prefix_and_diverges_suffix(self): + original = self.events[MUTATION_INDEX] + values = { + field: getattr(original, field) for field in original.__dataclass_fields__ + } + values["instrument_status"] = "suspended" + self._assert_event_mutation("instrument_metadata", original.__class__(**values)) + + def test_synthetic_balance_mutation_preserves_prefix_and_diverges_suffix(self): + payload = scenario_account_payload(self.base_snapshot) + payload["balances"][0]["available"] = Decimal("9000") + mutated = _snapshot_with_payload(self.base_snapshot, payload) + self._assert_account_mutation("synthetic_balance", mutated) + + def test_kill_switch_mutation_preserves_prefix_and_diverges_suffix(self): + payload = scenario_account_payload(self.base_snapshot) + payload["kill_switch_active"] = True + mutated = _snapshot_with_payload(self.base_snapshot, payload) + self._assert_account_mutation("kill_switch", mutated) + + def test_pending_order_mutation_preserves_prefix_and_diverges_suffix(self): + payload = scenario_account_payload(self.base_snapshot) + payload["pending_orders"] = [ + { + "instrument": "BTC-USDT", + "side": "buy", + "quantity": Decimal("0.001"), + "reserved_notional": Decimal("50"), + } + ] + payload["reserved_notional"] = Decimal("50") + payload["balances"][0] = { + "asset": "USDT", + "total": Decimal("10000"), + "available": Decimal("9950"), + "reserved": Decimal("50"), + } + mutated = _snapshot_with_payload(self.base_snapshot, payload) + self._assert_account_mutation("pending_order", mutated) + + def test_daily_loss_mutation_preserves_prefix_and_diverges_suffix(self): + payload = scenario_account_payload(self.base_snapshot) + payload["daily_realized_pnl"] = Decimal("-510") + payload["current_equity"] = Decimal("9490") + mutated = _snapshot_with_payload(self.base_snapshot, payload) + self._assert_account_mutation("daily_loss", mutated) + + +class Phase8BShadowCampaignExecutableMutationTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.results = _run_mutation_matrix( + TEST_REPOSITORY_SHA, + parent_campaign_sha256=PARENT_CAMPAIGN_SHA256, + ) + + def test_exact_mutation_catalog_executes_shared_runtime_and_canonical_lineage(self): + self.assertEqual( + tuple(result["case"] for result in self.results), + _MUTATION_CASES, + ) + self.assertEqual( + passed_campaign_case_count( + {"mutation_results": self.results}, "mutation_results" + ), + len(_MUTATION_CASES), + ) + for result in self.results: + with self.subTest(case=result["case"]): + fact_keys, check_keys = CASE_RESULT_SCHEMAS[result["case"]] + self.assertEqual( + tuple(result), + ("case", *fact_keys, "checks", "passed", "result_sha256"), + ) + self.assertEqual(tuple(result["checks"]), check_keys) + self.assertTrue(all(result["checks"].values())) + self.assertTrue(result["checks"]["explicit_parent_mutation_lineage_bound"]) + self.assertTrue( + result["checks"]["no_mutated_campaign_authority_claimed"] + ) + + def test_mutation_lineages_are_distinct_and_parent_is_immutable(self): + self.assertEqual( + {result["parent_campaign_sha256"] for result in self.results}, + {PARENT_CAMPAIGN_SHA256}, + ) + self.assertEqual( + len({result["parent_policy_chain_sha256"] for result in self.results}), 1 + ) + self.assertEqual( + len({result["mutated_policy_chain_sha256"] for result in self.results}), + len(_MUTATION_CASES), + ) + self.assertEqual( + len({result["mutation_lineage_sha256"] for result in self.results}), + len(_MUTATION_CASES), + ) + + def test_parent_campaign_hash_must_be_an_exact_digest(self): + for value in (None, True, "F" * 64, "f" * 63): + with self.subTest(value=value), self.assertRaises(ValueError): + _run_mutation_matrix( + TEST_REPOSITORY_SHA, + parent_campaign_sha256=value, + ) + + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_postgres.py b/open-core/tests/test_phase8b_shadow_campaign_postgres.py new file mode 100644 index 0000000..86ed54a --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_postgres.py @@ -0,0 +1,566 @@ +from __future__ import annotations + +import os +import subprocess +import sys +import unittest +from concurrent.futures import ThreadPoolExecutor +from dataclasses import replace +from pathlib import Path +from threading import Barrier +from uuid import uuid4 + +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + synthetic_account_snapshot_for_event, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import campaign_event_id +from secure_eval_wrapper.live.migration_catalog import ( + CANONICAL_MIGRATION_ROWS, +) +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + ShadowCampaignInjectedCrash, + ShadowCampaignOperationFailure, + ShadowCampaignRuntime, + ShadowCampaignSpecConflict, + campaign_scenario_for_event, + derive_shadow_campaign_run_id, +) +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + _bounded_inputs, + _run_ids, +) +from secure_eval_wrapper.live.shadow_repository import ( + MIGRATION_0026_SHA256, + PostgresShadowRepository, + validate_shadow_campaign_database_name, +) + + +RUN = os.environ.get("RUN_POSTGRES_INTEGRATION", "").lower() == "true" +ROOT = Path(__file__).resolve().parents[2] +MIGRATOR = ROOT / "open-core" / "scripts" / "apply_postgres_migrations.py" +REPOSITORY_SHA = "a" * 40 + + +@unittest.skipUnless(RUN, "requires real PostgreSQL 16") +class Phase8BShadowCampaignPostgresTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + import psycopg + from psycopg import sql + + suffix = uuid4().hex[:8] + cls.databases = { + "primary": validate_shadow_campaign_database_name( + f"secure_eval_phase8b_shadow_campaign_p_{suffix}" + ), + "restart": validate_shadow_campaign_database_name( + f"secure_eval_phase8b_shadow_campaign_r_{suffix}" + ), + "concurrent": validate_shadow_campaign_database_name( + f"secure_eval_phase8b_shadow_campaign_c_{suffix}" + ), + } + cls.base = { + "host": os.environ["POSTGRES_HOST"], + "port": int(os.environ["POSTGRES_PORT"]), + "user": os.environ["POSTGRES_USER"], + "password": os.environ["POSTGRES_PASSWORD"], + "sslmode": os.environ.get("POSTGRES_SSLMODE", "disable"), + } + subprocess.run( + [ + sys.executable, + str(MIGRATOR), + "--database", + cls.databases["primary"], + "--create-database", + ], + cwd=ROOT, + env=os.environ.copy(), + check=True, + capture_output=True, + text=True, + ) + admin = psycopg.connect(**cls.base, dbname="postgres", autocommit=True) + try: + with admin.cursor() as cursor: + for key in ("restart", "concurrent"): + cursor.execute( + sql.SQL("CREATE DATABASE {} TEMPLATE {}").format( + sql.Identifier(cls.databases[key]), + sql.Identifier(cls.databases["primary"]), + ) + ) + finally: + admin.close() + + @classmethod + def tearDownClass(cls): + import psycopg + from psycopg import sql + + admin = psycopg.connect(**cls.base, dbname="postgres", autocommit=True) + try: + with admin.cursor() as cursor: + for name in cls.databases.values(): + validate_shadow_campaign_database_name(name) + cursor.execute( + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity " + "WHERE datname=%s AND pid<>pg_backend_pid()", + (name,), + ) + cursor.execute( + sql.SQL("DROP DATABASE IF EXISTS {}").format( + sql.Identifier(name) + ) + ) + finally: + admin.close() + + @classmethod + def connect(cls, key): + import psycopg + from psycopg.rows import dict_row + + return psycopg.connect( + **cls.base, + dbname=cls.databases[key], + row_factory=dict_row, + ) + + @classmethod + def runtime(cls, key, spec, timeline): + connection = cls.connect(key) + repository = PostgresShadowRepository( + connection, + expected_database=cls.databases[key], + expected_host=cls.base["host"], + ) + return connection, ShadowCampaignRuntime( + repository, + REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + + def setUp(self): + for key in self.databases: + connection = self.connect(key) + try: + with connection.cursor() as cursor: + cursor.execute( + "DELETE FROM audit.run_manifests " + "WHERE storage_ref='phase8b_shadow_assurance'" + ) + connection.commit() + finally: + connection.close() + + def test_2016_event_run_replay_restart_and_earliest_gap_resume(self): + bounded_spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-2016-v1", + segment_count=1, + events_per_segment=2_016, + ) + spec = replace( + bounded_spec, + equivalent_duration_days=7, + window_labels=("7d",), + window_event_counts=(2_016,), + ) + connection, runtime = self.runtime("restart", spec, timeline) + baseline = runtime.run() + self.assertTrue(baseline.complete) + self.assertEqual(baseline.persisted_count, 2_016) + self.assertEqual(spec.equivalent_duration_days, 7) + self.assertEqual( + tuple(label for label, _ in baseline.window_result_hashes), + ("7d",), + ) + self.assertEqual( + runtime.repository.row_counts()["audit.run_manifests"], 2_016 + ) + connection.close() + + connection, restarted = self.runtime("restart", spec, timeline) + progress = restarted.inspect() + replayed = restarted.replay() + self.assertTrue(progress.complete) + self.assertEqual(replayed.replay_count, 2_016) + self.assertEqual(replayed.persisted_count, 0) + self.assertEqual( + replayed.campaign_result_sha256, baseline.campaign_result_sha256 + ) + run_ids = _run_ids(spec, timeline, REPOSITORY_SHA) + with connection.cursor() as cursor: + cursor.execute( + "DELETE FROM audit.run_manifests WHERE run_id=%s", + (run_ids[1_008],), + ) + connection.commit() + connection.close() + + connection, gap_runtime = self.runtime("restart", spec, timeline) + gap = gap_runtime.inspect() + self.assertEqual(gap.first_missing_index, 1_008) + self.assertEqual(gap.completed_prefix_count, 1_008) + resumed = gap_runtime.resume() + self.assertTrue(resumed.complete) + self.assertEqual(resumed.persisted_count, 1) + self.assertEqual(resumed.replay_count, 2_016 - 1_009) + self.assertEqual( + resumed.campaign_result_sha256, baseline.campaign_result_sha256 + ) + self.assertEqual( + gap_runtime.repository.row_counts()["audit.run_manifests"], 2_016 + ) + connection.close() + + def test_same_campaign_id_different_spec_conflicts_without_new_rows(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-conflict-v1", + segment_count=1, + events_per_segment=24, + ) + connection, runtime = self.runtime("primary", spec, timeline) + runtime.run() + changed_segment = replace( + spec.segments[0], seed=spec.segments[0].seed + 1 + ) + changed_spec = replace(spec, segments=(changed_segment,)) + with self.assertRaises(ShadowCampaignSpecConflict): + ShadowCampaignRuntime( + runtime.repository, + REPOSITORY_SHA, + spec=changed_spec, + account_timeline=timeline, + ).run() + self.assertEqual( + runtime.repository.row_counts()["audit.run_manifests"], 24 + ) + connection.close() + + def test_same_logical_event_rejects_changed_repository_authority(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-logical-authority-v1", + segment_count=1, + events_per_segment=24, + ) + connection, runtime = self.runtime("primary", spec, timeline) + runtime.run() + connection.close() + + connection = self.connect("primary") + repository = PostgresShadowRepository( + connection, + expected_database=self.databases["primary"], + expected_host=self.base["host"], + ) + changed_repository_runtime = ShadowCampaignRuntime( + repository, + "b" * 40, + spec=spec, + account_timeline=timeline, + ) + try: + with self.assertRaises(ShadowCampaignSpecConflict): + changed_repository_runtime.run() + self.assertEqual( + repository.row_counts()["audit.run_manifests"], 24 + ) + finally: + connection.close() + + def test_canonical_extra_event_authority_is_never_ignored(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-extra-authority-v1", + segment_count=1, + events_per_segment=24, + ) + connection, runtime = self.runtime("primary", spec, timeline) + runtime.run() + event = next(iter_shadow_campaign_events(spec, stop_index=1)) + account = synthetic_account_snapshot_for_event(0, timeline) + extra_event = replace( + event, + event_index=99_999, + event_id=campaign_event_id(event_index=99_999), + ) + extra_run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=extra_event.event_index, + event_sha256=extra_event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + with runtime.repository.campaign_authority(spec.campaign_id): + runtime._runtime.run_generated_fixture_event( + campaign_scenario_for_event(extra_event, account), + campaign_event_hash=extra_event.event_sha256, + timeframe=extra_event.timeframe, + shadow_run_id=extra_run_id, + ) + self.assertEqual( + runtime.repository.row_counts()["audit.run_manifests"], 25 + ) + with self.assertRaises(ShadowCampaignSpecConflict): + runtime.inspect() + with self.assertRaises(ShadowCampaignSpecConflict): + runtime.verify() + connection.close() + + def test_matching_campaign_corruption_fails_closed_without_repair(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-corruption-v1", + segment_count=1, + events_per_segment=24, + ) + connection, runtime = self.runtime("primary", spec, timeline) + runtime.run() + run_id = _run_ids(spec, timeline, REPOSITORY_SHA)[7] + with connection.cursor() as cursor: + cursor.execute( + "UPDATE audit.run_manifests SET manifest_jsonb=" + "jsonb_set(manifest_jsonb,'{bundle_hash}',to_jsonb(%s::text),false) " + "WHERE run_id=%s", + ("0" * 64, run_id), + ) + connection.commit() + for operation in ( + runtime.inspect, + runtime.run, + runtime.resume, + runtime.replay, + ): + with self.subTest(operation=operation.__name__): + with self.assertRaises(ShadowCampaignOperationFailure) as caught: + operation() + self.assertEqual( + caught.exception.failure_stage, "repository_validation" + ) + self.assertEqual( + caught.exception.progress["first_missing_index"], 7 + ) + self.assertEqual(caught.exception.progress["processed_event_count"], 0) + self.assertEqual(caught.exception.progress["committed_row_count"], 0) + with connection.cursor() as cursor: + cursor.execute( + "SELECT manifest_jsonb->>'bundle_hash' AS bundle_hash " + "FROM audit.run_manifests WHERE run_id=%s", + (run_id,), + ) + row = cursor.fetchone() + connection.rollback() + self.assertEqual(row["bundle_hash"], "0" * 64) + self.assertEqual(runtime.repository.row_counts()["audit.run_manifests"], 24) + connection.close() + + def test_exact_postgresql16_disposable_target_and_immutable_catalog(self): + connection = self.connect("primary") + try: + repository = PostgresShadowRepository( + connection, + expected_database=self.databases["primary"], + expected_host=self.base["host"], + ) + self.assertEqual(repository.authoritative_storage, "PostgreSQL") + with connection.cursor() as cursor: + cursor.execute( + "SELECT current_database() AS database_name, " + "current_setting('server_version_num')::integer " + "AS server_version_num" + ) + identity = cursor.fetchone() + cursor.execute( + "SELECT migration_id,filename,sha256::text AS sha256 " + "FROM audit.schema_migrations ORDER BY migration_id" + ) + migrations = tuple(cursor.fetchall()) + connection.rollback() + self.assertEqual(identity["database_name"], self.databases["primary"]) + self.assertGreaterEqual(identity["server_version_num"], 160_000) + self.assertLess(identity["server_version_num"], 170_000) + self.assertEqual( + tuple( + (row["migration_id"], row["filename"], row["sha256"]) + for row in migrations + ), + CANONICAL_MIGRATION_ROWS, + ) + self.assertEqual(migrations[-1]["sha256"], MIGRATION_0026_SHA256) + finally: + connection.close() + + def test_two_connection_run_and_resume_are_idempotent(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-concurrent-v1", + segment_count=2, + events_per_segment=12, + ) + seed_connection, seed = self.runtime("concurrent", spec, timeline) + seed.run(stop_index=5) + seed_connection.close() + left_connection, left = self.runtime("concurrent", spec, timeline) + right_connection, right = self.runtime("concurrent", spec, timeline) + try: + with ThreadPoolExecutor(max_workers=2) as pool: + run_future = pool.submit(left.run) + resume_future = pool.submit(right.resume) + outcomes = (run_future.result(), resume_future.result()) + self.assertTrue(all(result.complete for result in outcomes)) + self.assertEqual( + outcomes[0].campaign_result_sha256, + outcomes[1].campaign_result_sha256, + ) + self.assertEqual( + left.repository.row_counts()["audit.run_manifests"], 24 + ) + finally: + left_connection.close() + right_connection.close() + + def test_same_repository_instance_and_connection_serialize_two_threads(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-one-connection-v1", + segment_count=2, + events_per_segment=12, + ) + connection, left = self.runtime("concurrent", spec, timeline) + right = ShadowCampaignRuntime( + left.repository, + REPOSITORY_SHA, + spec=spec, + account_timeline=timeline, + ) + start = Barrier(2) + + def execute(operation): + start.wait(timeout=10) + return operation() + + try: + with ThreadPoolExecutor(max_workers=2) as pool: + futures = ( + pool.submit(execute, left.run), + pool.submit(execute, right.resume), + ) + outcomes = tuple(future.result(timeout=60) for future in futures) + self.assertTrue(all(result.complete for result in outcomes)) + self.assertEqual( + {result.campaign_result_sha256 for result in outcomes}, + {outcomes[0].campaign_result_sha256}, + ) + self.assertEqual( + sorted(result.persisted_count for result in outcomes), + [0, spec.event_count], + ) + self.assertEqual( + sorted(result.replay_count for result in outcomes), + [0, spec.event_count], + ) + self.assertEqual( + left.repository.row_counts()["audit.run_manifests"], + spec.event_count, + ) + finally: + connection.close() + + def test_two_connection_same_campaign_id_different_spec_has_one_authority(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-concurrent-spec-v1", + segment_count=2, + events_per_segment=12, + ) + changed_spec = replace( + spec, + segments=( + replace(spec.segments[0], seed=spec.segments[0].seed + 1), + spec.segments[1], + ), + ) + left_connection, left = self.runtime("concurrent", spec, timeline) + right_connection, right = self.runtime( + "concurrent", changed_spec, timeline + ) + start = Barrier(2) + + def execute(candidate): + start.wait(timeout=10) + try: + return "complete", candidate.run() + except ShadowCampaignSpecConflict: + return "conflict", None + + try: + with ThreadPoolExecutor(max_workers=2) as pool: + futures = (pool.submit(execute, left), pool.submit(execute, right)) + outcomes = tuple(future.result(timeout=60) for future in futures) + self.assertCountEqual( + (status for status, _ in outcomes), + ("complete", "conflict"), + ) + winning_result = next( + result for status, result in outcomes if status == "complete" + ) + self.assertIsNotNone(winning_result) + self.assertTrue(winning_result.complete) + winning_spec_sha256 = ( + spec.spec_sha256 + if outcomes[0][0] == "complete" + else changed_spec.spec_sha256 + ) + self.assertEqual( + left.repository.observed_campaign_spec_hashes(spec.campaign_id), + frozenset({winning_spec_sha256}), + ) + self.assertEqual( + left.repository.row_counts()["audit.run_manifests"], + spec.event_count, + ) + finally: + left_connection.close() + right_connection.close() + + def test_campaign_postcommit_crash_recovers_in_fresh_process(self): + spec, timeline = _bounded_inputs( + campaign_id="phase8b-shadow-pg-crash-v1", + segment_count=1, + events_per_segment=12, + ) + connection, runtime = self.runtime("primary", spec, timeline) + with self.assertRaises(ShadowCampaignInjectedCrash) as caught: + runtime.run( + crash_at="after_event_bundle_commit", + crash_event_index=4, + ) + self.assertEqual(caught.exception.progress["committed_row_count"], 5) + self.assertIs(caught.exception.progress["complete"], False) + self.assertIsNone(caught.exception.progress["campaign_result_sha256"]) + self.assertEqual(caught.exception.progress["network_write_count"], 0) + self.assertEqual(caught.exception.progress["production_write_count"], 0) + connection.close() + + connection, restarted = self.runtime("primary", spec, timeline) + self.assertEqual(restarted.inspect().first_missing_index, 5) + recovered = restarted.resume() + self.assertTrue(recovered.complete) + replayed = restarted.replay() + self.assertEqual(replayed.persisted_count, 0) + self.assertEqual(replayed.replay_count, spec.event_count) + self.assertEqual( + replayed.campaign_result_sha256, + recovered.campaign_result_sha256, + ) + self.assertEqual( + restarted.repository.row_counts()["audit.run_manifests"], 12 + ) + connection.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_replay.py b/open-core/tests/test_phase8b_shadow_campaign_replay.py new file mode 100644 index 0000000..0edfdc7 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_replay.py @@ -0,0 +1,402 @@ +from __future__ import annotations + +import unittest +from decimal import Decimal +from dataclasses import replace +from unittest.mock import patch + +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + synthetic_account_snapshot_for_event, + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import ( + ShadowCampaignSpec, + SyntheticAccountTimelineSnapshot, + campaign_event_id, + synthetic_timeline_snapshot_id, +) +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + ShadowCampaignRuntime, + ShadowCampaignSpecConflict, + campaign_scenario_for_event, + derive_shadow_campaign_run_id, +) +from secure_eval_wrapper.live.shadow_repository import ( + MemoryShadowRepository, + ShadowCampaignPersistenceConflict, + ShadowMemoryStore, + ShadowPersistenceConflict, +) +from secure_eval_wrapper.live.shadow_runtime import GeneratedShadowScenarioSpec +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + + +EVENT_COUNT = 12 + + +def _small_spec() -> ShadowCampaignSpec: + canonical = FULL_90D_CAMPAIGN_SPEC + segment = replace( + canonical.segments[0], + segment_id="replay-test-segment", + event_count=EVENT_COUNT, + ) + return ShadowCampaignSpec( + campaign_id="phase8b-shadow-replay-test-v1", + campaign_version=canonical.campaign_version, + corpus_generator_version=canonical.corpus_generator_version, + corpus_generator_sha256=canonical.corpus_generator_sha256, + instrument=canonical.instrument, + instrument_type=canonical.instrument_type, + timeframe=canonical.timeframe, + start_at_utc=canonical.start_at_utc, + event_count=EVENT_COUNT, + events_per_segment=EVENT_COUNT, + equivalent_duration_days=1, + segments=(segment,), + window_labels=("test-window",), + window_event_counts=(EVENT_COUNT,), + ) + + +def _context(spec: ShadowCampaignSpec, event_index: int = 0): + event = next( + iter_shadow_campaign_events( + spec, start_index=event_index, stop_index=event_index + 1 + ) + ) + account = synthetic_account_snapshot_for_event(event_index) + run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + return event, account, run_id + + +def _split_account_timeline( + *, mutate_after_event: int | None = None +) -> tuple[SyntheticAccountTimelineSnapshot, ...]: + first = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + split_index = 6 + prefix_payload = dict(first.runtime_payload) + suffix_payload = dict(first.runtime_payload) + if mutate_after_event is not None: + if mutate_after_event != split_index: + raise AssertionError("test timeline mutation index is fixed") + suffix_payload["reserved_notional"] = Decimal("1") + + def snapshot( + start: int, stop: int, payload: dict[str, object] + ) -> SyntheticAccountTimelineSnapshot: + snapshot_id = synthetic_timeline_snapshot_id( + state_name=first.state_name, + effective_start_event_index=start, + effective_end_event_index=stop, + runtime_payload=payload, + ) + return replace( + first, + snapshot_id=snapshot_id, + effective_start_event_index=start, + effective_end_event_index=stop, + runtime_payload=payload, + ) + + return ( + snapshot(0, split_index, prefix_payload), + snapshot( + split_index, + first.effective_end_event_index, + suffix_payload, + ), + *FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[1:], + ) + + +class Phase8BShadowCampaignReplayTests(unittest.TestCase): + def test_replay_uses_persisted_bundle_summary_and_creates_no_rows(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + first = campaign.run() + row_ids = frozenset(store.bundles) + mapping_identity = id(store.bundles) + second = campaign.replay() + self.assertTrue(second.complete) + self.assertEqual(second.persisted_count, 0) + self.assertEqual(second.replay_count, EVENT_COUNT) + self.assertEqual(frozenset(store.bundles), row_ids) + self.assertEqual(id(store.bundles), mapping_identity) + self.assertEqual( + second.event_sequence_sha256, first.event_sequence_sha256 + ) + self.assertEqual( + second.final_decision_chain_sha256, + first.final_decision_chain_sha256, + ) + self.assertEqual( + second.campaign_result_sha256, first.campaign_result_sha256 + ) + + event, account, run_id = _context(spec) + canonical = repository.load_bundle(run_id) + replay_summary = campaign._runtime.run_generated_fixture_event( + campaign_scenario_for_event(event, account), + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=run_id, + ) + self.assertTrue(replay_summary.replayed) + self.assertNotEqual( + replay_summary.summary_hash, + canonical["summary"]["summary_hash"], + ) + third = campaign.replay() + self.assertEqual( + third.final_decision_chain_sha256, + first.final_decision_chain_sha256, + ) + self.assertEqual(len(store.bundles), EVENT_COUNT) + self.assertTrue(campaign.inspect().complete) + + def test_same_run_id_with_different_generated_input_is_a_conflict(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + campaign.run() + event, account, run_id = _context(spec) + original = repository.load_bundle(run_id) + scenario = campaign_scenario_for_event(event, account) + request = dict(scenario.request_payload) + request["quantity"] = "0.0099" + conflicting = GeneratedShadowScenarioSpec( + scenario.scenario_id, + scenario.category, + scenario.account_payload, + scenario.market_payload, + request, + ) + with self.assertRaises(ShadowPersistenceConflict): + campaign._runtime.run_generated_fixture_event( + conflicting, + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=run_id, + ) + self.assertEqual(repository.load_bundle(run_id), original) + self.assertEqual(len(store.bundles), EVENT_COUNT) + + def test_canonical_extra_event_authority_is_never_ignored(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + campaign.run() + event, account, _ = _context(spec) + extra_event = replace( + event, + event_index=99_999, + event_id=campaign_event_id(event_index=99_999), + ) + extra_run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=extra_event.event_index, + event_sha256=extra_event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + with repository.campaign_authority(spec.campaign_id): + campaign._runtime.run_generated_fixture_event( + campaign_scenario_for_event(extra_event, account), + campaign_event_hash=extra_event.event_sha256, + timeframe=extra_event.timeframe, + shadow_run_id=extra_run_id, + ) + self.assertEqual(len(store.bundles), EVENT_COUNT + 1) + with self.assertRaises(ShadowCampaignSpecConflict): + campaign.inspect() + with self.assertRaises(ShadowCampaignSpecConflict): + campaign.verify() + + def test_same_spec_rejects_changed_timeline_and_repository_at_divergence(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + baseline_runtime = ShadowCampaignRuntime( + repository, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=_split_account_timeline(), + ) + baseline_contexts = tuple(baseline_runtime._contexts(0, 7)) + baseline_runtime.run() + row_ids = frozenset(store.bundles) + + changed_runtime = ShadowCampaignRuntime( + MemoryShadowRepository(store), + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=_split_account_timeline( + mutate_after_event=6 + ), + ) + changed_contexts = tuple(changed_runtime._contexts(0, 7)) + self.assertEqual( + tuple(context.shadow_run_id for context in baseline_contexts[:6]), + tuple(context.shadow_run_id for context in changed_contexts[:6]), + ) + self.assertNotEqual( + baseline_contexts[6].shadow_run_id, + changed_contexts[6].shadow_run_id, + ) + with self.assertRaises(ShadowCampaignSpecConflict): + changed_runtime.run() + self.assertEqual(frozenset(store.bundles), row_ids) + self.assertNotIn(changed_contexts[6].shadow_run_id, store.bundles) + + changed_repository = ShadowCampaignRuntime( + MemoryShadowRepository(store), + "b" * 40, + spec=spec, + account_timeline=_split_account_timeline(), + ) + with self.assertRaises(ShadowCampaignSpecConflict): + changed_repository.run() + self.assertEqual(frozenset(store.bundles), row_ids) + + def test_verify_reexecutes_and_rejects_self_consistent_tampering(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + baseline = campaign.run() + event, account, run_id = _context(spec) + canonical = repository.load_bundle(run_id) + scenario = campaign_scenario_for_event(event, account) + request = dict(scenario.request_payload) + request["quantity"] = "0.0099" + conflicting = GeneratedShadowScenarioSpec( + scenario.scenario_id, + scenario.category, + scenario.account_payload, + scenario.market_payload, + request, + ) + + isolated_repository = MemoryShadowRepository() + isolated_campaign = ShadowCampaignRuntime( + isolated_repository, TEST_REPOSITORY_SHA, spec=spec + ) + with isolated_repository.campaign_authority(spec.campaign_id): + isolated_campaign._runtime.run_generated_fixture_event( + conflicting, + campaign_event_hash=event.event_sha256, + timeframe=event.timeframe, + shadow_run_id=run_id, + ) + tampered = isolated_repository.load_bundle(run_id) + self.assertIsNotNone(tampered) + self.assertNotEqual(tampered["bundle_hash"], canonical["bundle_hash"]) + with store.lock: + store.bundles[run_id] = tampered + + inspected = campaign.inspect() + self.assertTrue(inspected.complete) + self.assertNotEqual( + inspected.decision_chain_sha256, + baseline.final_decision_chain_sha256, + ) + self.assertNotEqual( + inspected.campaign_result_sha256, baseline.campaign_result_sha256 + ) + with self.assertRaises(ShadowPersistenceConflict): + campaign.verify() + + def test_inflight_authority_conflict_preserves_six_commits_and_progress(self): + spec = _small_spec() + repository = MemoryShadowRepository(ShadowMemoryStore()) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + original = MemoryShadowRepository.persist_bundle + calls = 0 + + def conflict_on_seventh(instance, *args, **kwargs): + nonlocal calls + calls += 1 + if calls == 7: + raise ShadowCampaignPersistenceConflict( + "injected public-safe logical authority conflict" + ) + return original(instance, *args, **kwargs) + + with patch.object( + MemoryShadowRepository, + "persist_bundle", + new=conflict_on_seventh, + ): + with self.assertRaises(ShadowCampaignSpecConflict) as raised: + campaign.run() + self.assertEqual(raised.exception.failure_stage, "event_execution") + self.assertEqual( + ( + raised.exception.progress["processed_event_count"], + raised.exception.progress["committed_row_count"], + raised.exception.progress["first_missing_index"], + ), + (6, 6, 6), + ) + self.assertEqual(repository.row_counts()["audit.run_manifests"], 6) + + def test_same_campaign_id_different_spec_fails_before_event_generation(self): + spec = _small_spec() + store = ShadowMemoryStore() + first_repository = MemoryShadowRepository(store) + ShadowCampaignRuntime( + first_repository, TEST_REPOSITORY_SHA, spec=spec + ).run() + changed_segment = replace( + spec.segments[0], seed=spec.segments[0].seed + 1 + ) + conflicting_spec = replace(spec, segments=(changed_segment,)) + self.assertNotEqual(conflicting_spec.spec_sha256, spec.spec_sha256) + + fresh_process = ShadowCampaignRuntime( + MemoryShadowRepository(store), + TEST_REPOSITORY_SHA, + spec=conflicting_spec, + ) + with patch( + "secure_eval_wrapper.live.shadow_campaign_runtime.iter_shadow_campaign_events", + side_effect=AssertionError("cross-spec guard must precede iteration"), + ): + with self.assertRaises(ShadowCampaignSpecConflict): + fresh_process.run() + self.assertEqual(len(store.bundles), EVENT_COUNT) + self.assertEqual( + first_repository.observed_campaign_spec_hashes(spec.campaign_id), + frozenset({spec.spec_sha256}), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_restart.py b/open-core/tests/test_phase8b_shadow_campaign_restart.py new file mode 100644 index 0000000..1bde27a --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_restart.py @@ -0,0 +1,329 @@ +from __future__ import annotations + +import unittest +from copy import deepcopy +from dataclasses import replace +from unittest.mock import patch +from uuid import UUID + +from secure_eval_wrapper.live.shadow_bundle import ShadowBundleValidationError +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + synthetic_account_snapshot_for_event, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import ShadowCampaignSpec +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + CAMPAIGN_CRASH_POINTS, + ShadowCampaignInjectedCrash, + ShadowCampaignOperationFailure, + ShadowCampaignRuntime, + derive_shadow_campaign_run_id, +) +from secure_eval_wrapper.live.shadow_repository import ( + MemoryShadowRepository, + ShadowMemoryStore, + ShadowPostCommitCrash, +) +from secure_eval_wrapper.live.shadow_campaign_verifier import ( + _run_corruption_rejection, +) +from secure_eval_wrapper.live.shadow_runtime import RUNTIME_CRASH_POINTS +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + + +EVENT_COUNT = 12 + + +def _small_spec() -> ShadowCampaignSpec: + canonical = FULL_90D_CAMPAIGN_SPEC + segment = replace( + canonical.segments[0], + segment_id="restart-test-segment", + event_count=EVENT_COUNT, + ) + return ShadowCampaignSpec( + campaign_id="phase8b-shadow-restart-test-v1", + campaign_version=canonical.campaign_version, + corpus_generator_version=canonical.corpus_generator_version, + corpus_generator_sha256=canonical.corpus_generator_sha256, + instrument=canonical.instrument, + instrument_type=canonical.instrument_type, + timeframe=canonical.timeframe, + start_at_utc=canonical.start_at_utc, + event_count=EVENT_COUNT, + events_per_segment=EVENT_COUNT, + equivalent_duration_days=1, + segments=(segment,), + window_labels=("test-window",), + window_event_counts=(EVENT_COUNT,), + ) + + +def _run_id(spec: ShadowCampaignSpec, event_index: int): + event = next( + iter_shadow_campaign_events( + spec, start_index=event_index, stop_index=event_index + 1 + ) + ) + account = synthetic_account_snapshot_for_event(event_index) + return derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + + +def _fresh_restart_repository(source: MemoryShadowRepository) -> MemoryShadowRepository: + with source.store.lock: + bundles = deepcopy(source.store.bundles) + restarted = MemoryShadowRepository(ShadowMemoryStore(bundles=bundles)) + with restarted.store.lock: + if restarted.store.campaign_spec_bindings or restarted.store.campaign_locks: + raise AssertionError("restart repository copied mutable authority caches") + if any( + restarted.store.bundles[run_id] is source.store.bundles[run_id] + for run_id in bundles + ): + raise AssertionError("restart repository shares mutable bundle objects") + return restarted + + +class Phase8BShadowCampaignRestartTests(unittest.TestCase): + def test_campaign_postcommit_crash_resumes_from_first_uncommitted_event(self): + self.assertEqual(len(CAMPAIGN_CRASH_POINTS), 10) + self.assertEqual(len(RUNTIME_CRASH_POINTS), 9) + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + with self.assertRaises(ShadowCampaignInjectedCrash) as caught: + campaign.run( + crash_at="after_event_bundle_commit", crash_event_index=4 + ) + self.assertEqual(caught.exception.progress["committed_row_count"], 5) + self.assertEqual(caught.exception.progress["first_missing_index"], 5) + self.assertEqual(repository.row_counts()["audit.run_manifests"], 5) + + restarted_repository = _fresh_restart_repository(repository) + restarted = ShadowCampaignRuntime( + restarted_repository, TEST_REPOSITORY_SHA, spec=spec + ) + self.assertIsNot(restarted_repository.store, store) + progress = restarted.inspect() + self.assertEqual(progress.completed_prefix_count, 5) + self.assertEqual(progress.first_missing_index, 5) + resumed = restarted.resume() + self.assertTrue(resumed.complete) + self.assertEqual(resumed.persisted_count, 7) + self.assertEqual(resumed.replay_count, 0) + self.assertEqual(len(restarted_repository.store.bundles), EVENT_COUNT) + self.assertEqual(len(store.bundles), 5) + + clean = ShadowCampaignRuntime( + MemoryShadowRepository(), TEST_REPOSITORY_SHA, spec=spec + ).run() + self.assertEqual( + resumed.final_decision_chain_sha256, + clean.final_decision_chain_sha256, + ) + self.assertEqual( + resumed.campaign_result_sha256, clean.campaign_result_sha256 + ) + + def test_existing_runtime_postcommit_crash_remains_resumable(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + with self.assertRaises(ShadowPostCommitCrash): + campaign.run( + crash_at="after_transaction_commit_before_response", + crash_event_index=4, + ) + self.assertEqual(len(store.bundles), 5) + restarted_repository = _fresh_restart_repository(repository) + restarted = ShadowCampaignRuntime( + restarted_repository, TEST_REPOSITORY_SHA, spec=spec + ) + self.assertEqual(restarted.inspect().first_missing_index, 5) + self.assertTrue(restarted.resume().complete) + self.assertEqual(len(restarted_repository.store.bundles), EVENT_COUNT) + self.assertEqual(len(store.bundles), 5) + + def test_sparse_gap_resumes_from_earliest_gap_without_duplicate_rows(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + baseline = campaign.run() + missing_id = _run_id(spec, 3) + with store.lock: + del store.bundles[missing_id] + restarted_repository = _fresh_restart_repository(repository) + restarted = ShadowCampaignRuntime( + restarted_repository, TEST_REPOSITORY_SHA, spec=spec + ) + progress = restarted.inspect() + self.assertEqual(progress.first_missing_index, 3) + self.assertEqual(progress.completed_prefix_count, 3) + self.assertEqual(progress.missing_event_count, 1) + + resumed = restarted.resume() + self.assertTrue(resumed.complete) + self.assertEqual(resumed.persisted_count, 1) + self.assertEqual(resumed.replay_count, EVENT_COUNT - 4) + self.assertEqual(len(restarted_repository.store.bundles), EVENT_COUNT) + self.assertEqual(len(store.bundles), EVENT_COUNT - 1) + self.assertEqual( + resumed.campaign_result_sha256, baseline.campaign_result_sha256 + ) + + def test_sparse_gap_crash_reports_actual_repository_first_missing(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + campaign.run() + with store.lock: + del store.bundles[_run_id(spec, 3)] + + restarted_repository = _fresh_restart_repository(repository) + restarted = ShadowCampaignRuntime( + restarted_repository, TEST_REPOSITORY_SHA, spec=spec + ) + with self.assertRaises(ShadowCampaignInjectedCrash) as caught: + restarted.resume( + crash_at="after_event_bundle_commit", + crash_event_index=3, + ) + self.assertEqual(caught.exception.progress["committed_row_count"], 1) + self.assertEqual(caught.exception.progress["replay_count"], 0) + self.assertIsNone(caught.exception.progress["first_missing_index"]) + self.assertEqual(len(restarted_repository.store.bundles), EVENT_COUNT) + self.assertEqual(len(store.bundles), EVENT_COUNT - 1) + + def test_complete_run_rescans_and_rejects_midrun_corruption(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + original_load = repository.load_bundle + corrupted_once = False + + def corrupt_after_load(shadow_run_id): + nonlocal corrupted_once + bundle = original_load(shadow_run_id) + if bundle is not None and not corrupted_once: + corrupted_once = True + with store.lock: + corrupted = dict(store.bundles[shadow_run_id]) + corrupted["bundle_hash"] = "0" * 64 + store.bundles[shadow_run_id] = corrupted + return bundle + + with ( + patch.object( + repository, + "load_bundle", + side_effect=corrupt_after_load, + ), + self.assertRaises(ShadowCampaignOperationFailure) as caught, + ): + campaign.run() + self.assertEqual( + caught.exception.failure_stage, + "final_repository_validation", + ) + self.assertEqual(len(store.bundles), EVENT_COUNT) + + def test_corruption_fails_closed_without_repair_or_aggregate_row(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + campaign.run() + corrupt_id = _run_id(spec, 4) + with store.lock: + corrupted = dict(store.bundles[corrupt_id]) + corrupted["bundle_hash"] = "0" * 64 + store.bundles[corrupt_id] = corrupted + row_count = repository.row_counts()["audit.run_manifests"] + with self.assertRaises(ShadowCampaignOperationFailure) as inspect_failure: + campaign.inspect() + with self.assertRaises(ShadowCampaignOperationFailure) as resume_failure: + campaign.resume() + for failure in (inspect_failure.exception, resume_failure.exception): + self.assertEqual(failure.failure_stage, "repository_validation") + self.assertEqual(failure.progress["first_missing_index"], 4) + self.assertEqual(failure.progress["processed_event_count"], 0) + self.assertEqual(failure.progress["committed_row_count"], 0) + self.assertEqual(failure.progress["replay_count"], 0) + self.assertFalse(failure.progress["complete"]) + self.assertEqual( + repository.row_counts()["audit.run_manifests"], row_count + ) + self.assertEqual(store.bundles[corrupt_id]["bundle_hash"], "0" * 64) + self.assertTrue( + all( + payload.get("operation") == "phase8b_shadow_assurance" + for payload in store.bundles.values() + if payload.get("status") == "complete" + ) + ) + + def test_verifier_rejects_corruption_with_exact_structured_progress(self): + self.assertEqual( + _run_corruption_rejection(TEST_REPOSITORY_SHA), + 1, + ) + + def test_row_key_corruption_is_structured_and_never_repaired(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + campaign.run() + corrupt_id = _run_id(spec, 4) + wrong_id = UUID("00000000-0000-5000-8000-00000000fffe") + with store.lock: + payload = store.bundles.pop(corrupt_id) + store.bundles[wrong_id] = payload + row_count = repository.row_counts()["audit.run_manifests"] + + with self.assertRaises(ShadowCampaignOperationFailure) as caught: + campaign.inspect() + + failure = caught.exception + self.assertEqual(failure.failure_stage, "repository_validation") + self.assertEqual(failure.progress["first_missing_index"], 4) + self.assertEqual(failure.progress["processed_event_count"], 0) + self.assertEqual(failure.progress["committed_row_count"], 0) + self.assertEqual( + repository.row_counts()["audit.run_manifests"], row_count + ) + self.assertNotIn(corrupt_id, store.bundles) + self.assertIs(store.bundles[wrong_id], payload) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_runtime.py b/open-core/tests/test_phase8b_shadow_campaign_runtime.py new file mode 100644 index 0000000..467c793 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_runtime.py @@ -0,0 +1,551 @@ +from __future__ import annotations + +import socket +import unittest +from dataclasses import replace +from unittest.mock import patch + +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE, + synthetic_account_snapshot_for_event, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + FULL_90D_CAMPAIGN_SPEC, + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_models import ( + ShadowCampaignSpec, + synthetic_timeline_snapshot_id, +) +from secure_eval_wrapper.live.shadow_campaign_runtime import ( + ShadowCampaignRuntime, + campaign_policy_chain_seed, + campaign_policy_chain_sha256, + campaign_policy_chain_step, + campaign_policy_decision_sha256, + campaign_policy_hash_projection, + campaign_policy_input_sha256, + campaign_policy_summary_sha256, + campaign_scenario_for_event, + derive_shadow_campaign_run_id, +) +from secure_eval_wrapper.live.shadow_repository import ( + MemoryShadowRepository, + ShadowMemoryStore, +) +from secure_eval_wrapper.live.shadow_runtime import ( + GeneratedShadowScenarioSpec, + ShadowAuthorityError, +) +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA, runtime + + +EVENT_COUNT = 12 + + +def _small_spec() -> ShadowCampaignSpec: + canonical = FULL_90D_CAMPAIGN_SPEC + segment = replace( + canonical.segments[0], + segment_id="runtime-test-segment", + event_count=EVENT_COUNT, + ) + return ShadowCampaignSpec( + campaign_id="phase8b-shadow-runtime-test-v1", + campaign_version=canonical.campaign_version, + corpus_generator_version=canonical.corpus_generator_version, + corpus_generator_sha256=canonical.corpus_generator_sha256, + instrument=canonical.instrument, + instrument_type=canonical.instrument_type, + timeframe=canonical.timeframe, + start_at_utc=canonical.start_at_utc, + event_count=EVENT_COUNT, + events_per_segment=EVENT_COUNT, + equivalent_duration_days=1, + segments=(segment,), + window_labels=("test-window",), + window_event_counts=(EVENT_COUNT,), + ) + + + +def _lineage_specs() -> tuple[ShadowCampaignSpec, ShadowCampaignSpec]: + canonical = FULL_90D_CAMPAIGN_SPEC + prefix_segment = replace( + canonical.segments[0], + segment_id="runtime-policy-prefix", + event_count=4, + ) + future_segment = replace( + canonical.segments[1], + segment_id="runtime-policy-future-a", + start_event_index=4, + event_count=4, + ) + base = replace( + canonical, + campaign_id="phase8b-shadow-policy-lineage-a", + campaign_version="policy-lineage-a", + event_count=8, + events_per_segment=4, + equivalent_duration_days=1, + segments=(prefix_segment, future_segment), + window_labels=("prefix-a", "complete-a"), + window_event_counts=(4, 8), + ) + variant = replace( + base, + campaign_id="phase8b-shadow-policy-lineage-b", + campaign_version="policy-lineage-b", + segments=( + prefix_segment, + replace( + future_segment, + segment_id="runtime-policy-future-b", + regime="runtime-future-relabel", + seed=future_segment.seed + 10_003, + ), + ), + window_labels=("prefix-b", "complete-b"), + ) + return base, variant +class Phase8BShadowCampaignRuntimeTests(unittest.TestCase): + def test_generated_input_is_expectation_free_hash_bound_and_exact_5m(self): + spec = _small_spec() + event = next(iter_shadow_campaign_events(spec, stop_index=1)) + account = synthetic_account_snapshot_for_event(event.event_index) + scenario = campaign_scenario_for_event(event, account) + self.assertIs(type(scenario), GeneratedShadowScenarioSpec) + self.assertIn(f":account:{account.snapshot_sha256}", scenario.scenario_id) + self.assertEqual(scenario.request_payload["timeframe"], "5m") + self.assertEqual( + scenario.request_payload["campaign_event_hash"], event.event_sha256 + ) + self.assertFalse( + { + "regime", + "regime_label", + "segment_label", + "expected_result", + "expected_blockers", + "expected_shadow_intent_count", + } + & ( + set(scenario.account_payload) + | set(scenario.market_payload) + | set(scenario.request_payload) + ) + ) + + store = ShadowMemoryStore() + campaign = ShadowCampaignRuntime( + MemoryShadowRepository(store), TEST_REPOSITORY_SHA, spec=spec + ) + run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + self.assertEqual( + run_id, + derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ), + ) + with self.assertRaises(ShadowAuthorityError): + campaign._runtime.run_generated_fixture_event( + scenario, + campaign_event_hash="0" * 64, + timeframe="5m", + shadow_run_id=run_id, + ) + with self.assertRaises(ShadowAuthorityError): + campaign._runtime.run_generated_fixture_event( + scenario, + campaign_event_hash=event.event_sha256, + timeframe="1m", + shadow_run_id=run_id, + ) + self.assertEqual(store.bundles, {}) + + def test_full_small_campaign_uses_shared_runtime_without_mutating_timeline(self): + spec = _small_spec() + store = ShadowMemoryStore() + repository = MemoryShadowRepository(store) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + timeline_hashes = tuple( + snapshot.snapshot_sha256 + for snapshot in FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ) + mapping_identity = id(store.bundles) + with patch.object( + socket, "socket", side_effect=AssertionError("socket forbidden") + ): + result = campaign.run() + self.assertTrue(result.complete) + self.assertEqual(result.processed_event_count, EVENT_COUNT) + self.assertEqual(result.persisted_count, EVENT_COUNT) + self.assertEqual(result.replay_count, 0) + segment_counts = { + segment_id: dict(counts) + for segment_id, counts in result.segment_decision_counts.items() + } + self.assertEqual(set(segment_counts), {"runtime-test-segment"}) + self.assertEqual( + segment_counts["runtime-test-segment"], + {"accepted": result.accepted_decision_count, "blocked": result.blocked_decision_count}, + ) + self.assertEqual(repository.row_counts(), {"audit.run_manifests": EVENT_COUNT}) + self.assertEqual(id(store.bundles), mapping_identity) + self.assertTrue( + all( + bundle["operation"] == "phase8b_shadow_assurance" + for bundle in store.bundles.values() + ) + ) + self.assertEqual(result.safety_facts.network_read_count, 0) + self.assertEqual(result.safety_facts.network_write_count, 0) + self.assertEqual(result.safety_facts.credential_read_count, 0) + self.assertEqual( + timeline_hashes, + tuple( + snapshot.snapshot_sha256 + for snapshot in FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE + ), + ) + + def test_policy_chain_binds_synthetic_account_snapshot_identity(self): + spec = _small_spec() + repository = MemoryShadowRepository(ShadowMemoryStore()) + campaign = ShadowCampaignRuntime( + repository, TEST_REPOSITORY_SHA, spec=spec + ) + campaign.run(stop_index=1) + event = next(iter_shadow_campaign_events(spec, stop_index=1)) + account = synthetic_account_snapshot_for_event(0) + run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + bundle = repository.load_bundle(run_id) + changed_name = f"{account.state_name}-same-runtime" + changed = replace( + account, + snapshot_id=synthetic_timeline_snapshot_id( + state_name=changed_name, + effective_start_event_index=account.effective_start_event_index, + effective_end_event_index=account.effective_end_event_index, + runtime_payload=account.runtime_payload, + ), + state_name=changed_name, + ) + self.assertEqual( + account.runtime_payload_sha256, changed.runtime_payload_sha256 + ) + self.assertNotEqual(account.snapshot_sha256, changed.snapshot_sha256) + changed_repository = MemoryShadowRepository(ShadowMemoryStore()) + ShadowCampaignRuntime( + changed_repository, + TEST_REPOSITORY_SHA, + spec=spec, + account_timeline=( + changed, + *FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[1:], + ), + ).run(stop_index=1) + changed_run_id = derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=changed.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec.spec_sha256, + ) + changed_bundle = changed_repository.load_bundle(changed_run_id) + projection = campaign_policy_hash_projection(event, account, bundle) + changed_projection = campaign_policy_hash_projection( + event, changed, changed_bundle + ) + self.assertEqual(projection, changed_projection) + self.assertNotEqual( + campaign_policy_chain_step( + prior_chain_sha256=campaign_policy_chain_seed(), + event=event, + account=account, + projection=projection, + bundle=bundle, + ), + campaign_policy_chain_step( + prior_chain_sha256=campaign_policy_chain_seed(), + event=event, + account=changed, + projection=changed_projection, + bundle=changed_bundle, + ), + ) + + def test_policy_chain_ignores_lineage_while_authority_chain_binds_it(self): + spec_a, spec_b = _lineage_specs() + repository_a = MemoryShadowRepository(ShadowMemoryStore()) + repository_b = MemoryShadowRepository(ShadowMemoryStore()) + result_a = ShadowCampaignRuntime( + repository_a, + TEST_REPOSITORY_SHA, + spec=spec_a, + ).run(stop_index=4) + result_b = ShadowCampaignRuntime( + repository_b, + TEST_REPOSITORY_SHA, + spec=spec_b, + ).run(stop_index=4) + + self.assertFalse(result_a.complete) + self.assertFalse(result_b.complete) + self.assertEqual( + result_a.event_sequence_sha256, + result_b.event_sequence_sha256, + ) + self.assertNotEqual( + dict(result_a.segment_hashes), + dict(result_b.segment_hashes), + ) + self.assertNotEqual( + result_a.final_decision_chain_sha256, + result_b.final_decision_chain_sha256, + ) + + chain_a = campaign_policy_chain_seed() + chain_b = campaign_policy_chain_seed() + events_a = tuple(iter_shadow_campaign_events(spec_a, stop_index=4)) + events_b = tuple(iter_shadow_campaign_events(spec_b, stop_index=4)) + for event_a, event_b in zip(events_a, events_b): + self.assertEqual(event_a.event_id, event_b.event_id) + self.assertEqual(event_a.event_sha256, event_b.event_sha256) + account = synthetic_account_snapshot_for_event(event_a.event_index) + run_id_a = derive_shadow_campaign_run_id( + campaign_id=spec_a.campaign_id, + event_index=event_a.event_index, + event_sha256=event_a.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec_a.spec_sha256, + ) + run_id_b = derive_shadow_campaign_run_id( + campaign_id=spec_b.campaign_id, + event_index=event_b.event_index, + event_sha256=event_b.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=TEST_REPOSITORY_SHA, + campaign_spec_sha256=spec_b.spec_sha256, + ) + self.assertNotEqual(run_id_a, run_id_b) + bundle_a = repository_a.load_bundle(run_id_a) + bundle_b = repository_b.load_bundle(run_id_b) + self.assertNotEqual( + bundle_a["decision"]["decision_hash"], + bundle_b["decision"]["decision_hash"], + ) + + projection_a = campaign_policy_hash_projection( + event_a, + account, + bundle_a, + ) + projection_b = campaign_policy_hash_projection( + event_b, + account, + bundle_b, + ) + self.assertEqual(dict(projection_a), dict(projection_b)) + self.assertEqual( + projection_a["policy_input_sha256"], + campaign_policy_input_sha256(event_a, account), + ) + self.assertEqual( + projection_a["policy_decision_sha256"], + campaign_policy_decision_sha256( + projection_a["policy_input_sha256"], + bundle_a, + ), + ) + self.assertEqual( + projection_a["policy_summary_sha256"], + campaign_policy_summary_sha256( + projection_a["policy_input_sha256"], + projection_a["policy_decision_sha256"], + bundle_a, + ), + ) + + next_chain_a = campaign_policy_chain_step( + prior_chain_sha256=chain_a, + event=event_a, + account=account, + projection=projection_a, + bundle=bundle_a, + ) + next_chain_b = campaign_policy_chain_step( + prior_chain_sha256=chain_b, + event=event_b, + account=account, + projection=projection_b, + bundle=bundle_b, + ) + self.assertEqual( + next_chain_a, + campaign_policy_chain_sha256( + prior_chain_sha256=chain_a, + event=event_a, + account=account, + bundle=bundle_a, + ), + ) + self.assertEqual(next_chain_a, next_chain_b) + if event_a.event_index == 0: + tampered_projection = dict(projection_a) + tampered_projection["accepted"] = not projection_a["accepted"] + with self.assertRaisesRegex(ValueError, "not canonical"): + campaign_policy_chain_step( + prior_chain_sha256=chain_a, + event=event_a, + account=account, + projection=tampered_projection, + bundle=bundle_a, + ) + chain_a = next_chain_a + chain_b = next_chain_b + + self.assertEqual(chain_a, chain_b) + self.assertNotEqual(chain_a, result_a.final_decision_chain_sha256) + self.assertNotEqual(chain_b, result_b.final_decision_chain_sha256) + def test_dangerous_dependency_and_canonical_id_conflict_fail_before_iteration(self): + spec = _small_spec() + with patch( + "secure_eval_wrapper.live.shadow_campaign_runtime.iter_shadow_campaign_events", + side_effect=AssertionError("corpus must not be initialized"), + ): + with self.assertRaises(ShadowAuthorityError): + ShadowCampaignRuntime(object(), TEST_REPOSITORY_SHA, spec=spec) + + conflicting = replace( + spec, campaign_id=FULL_90D_CAMPAIGN_SPEC.campaign_id + ) + campaign = ShadowCampaignRuntime( + MemoryShadowRepository(), TEST_REPOSITORY_SHA, spec=conflicting + ) + with patch( + "secure_eval_wrapper.live.shadow_campaign_runtime.iter_shadow_campaign_events", + side_effect=AssertionError("corpus must not be initialized"), + ): + with self.assertRaisesRegex(ValueError, "conflicts"): + campaign.run() + + def test_canonical_campaign_rejects_a_mutated_first_claim_timeline(self): + first = FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[0] + mutated = ( + replace( + first, + expected_account_blockers=("mutated_canonical_expectation",), + ), + *FULL_90D_SYNTHETIC_ACCOUNT_TIMELINE[1:], + ) + with self.assertRaisesRegex(ValueError, "canonical synthetic account"): + ShadowCampaignRuntime( + MemoryShadowRepository(), + TEST_REPOSITORY_SHA, + spec=FULL_90D_CAMPAIGN_SPEC, + account_timeline=mutated, + ) + + def test_repository_sha_subclasses_are_rejected_at_public_boundaries(self): + class EvilSha(str): + comparisons = 0 + + def __eq__(self, other): + EvilSha.comparisons += 1 + raise AssertionError("forbidden repository SHA comparison executed") + + def __hash__(self): + EvilSha.comparisons += 1 + raise AssertionError("forbidden repository SHA hash executed") + + hostile = EvilSha(TEST_REPOSITORY_SHA) + spec = _small_spec() + event = next(iter_shadow_campaign_events(spec, stop_index=1)) + account = synthetic_account_snapshot_for_event(event.event_index) + with self.assertRaises(TypeError): + derive_shadow_campaign_run_id( + campaign_id=spec.campaign_id, + event_index=event.event_index, + event_sha256=event.event_sha256, + synthetic_account_snapshot_sha256=account.snapshot_sha256, + repository_sha=hostile, + campaign_spec_sha256=spec.spec_sha256, + ) + with self.assertRaises(TypeError): + ShadowCampaignRuntime( + MemoryShadowRepository(), + hostile, + spec=spec, + ) + valid_campaign = ShadowCampaignRuntime( + MemoryShadowRepository(), TEST_REPOSITORY_SHA, spec=spec + ) + result = valid_campaign.run(stop_index=0) + progress = valid_campaign.inspect() + for value in (result, progress): + with self.subTest(value_type=type(value).__name__), self.assertRaises( + TypeError + ): + replace(value, repository_sha=hostile) + self.assertEqual(EvilSha.comparisons, 0) + + def test_crash_point_subclass_is_rejected_before_comparison(self): + class EvilCrashPoint(str): + comparisons = 0 + + def __eq__(self, other): + EvilCrashPoint.comparisons += 1 + raise AssertionError("forbidden crash-point comparison executed") + + campaign = ShadowCampaignRuntime( + MemoryShadowRepository(), + TEST_REPOSITORY_SHA, + spec=_small_spec(), + ) + with self.assertRaises(ValueError): + campaign.run(stop_index=0, crash_at=EvilCrashPoint("before_first_event")) + self.assertEqual(EvilCrashPoint.comparisons, 0) + + def test_legacy_fixture_hashes_are_unchanged(self): + summary = runtime().run_fixture("clean_flat_account") + self.assertEqual( + summary.input_hash, + "4582a068559a0df34d6d17749af97835cd6508a1a55e60607b3fd5559760f7de", + ) + self.assertEqual( + summary.decision_hash, + "f4d621b7e2bd0ac67e889daec2bd7e2e80f25ebdc3945f9cc4500fba1c208188", + ) + self.assertEqual( + summary.summary_hash, + "aa75096f4d7553698108d8f6685360c704242a670a10c6f8ace45575c5177f4e", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_campaign_verifier_runtime.py b/open-core/tests/test_phase8b_shadow_campaign_verifier_runtime.py new file mode 100644 index 0000000..54ac848 --- /dev/null +++ b/open-core/tests/test_phase8b_shadow_campaign_verifier_runtime.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +import unittest +from dataclasses import replace + +from secure_eval_wrapper.live.shadow_campaign_accounts import ( + synthetic_account_snapshot_for_event, +) +from secure_eval_wrapper.live.shadow_campaign_corpus import ( + iter_shadow_campaign_events, +) +from secure_eval_wrapper.live.shadow_campaign_verifier_runtime import ( + compare_actual_runtime_streams, + execute_actual_same_run_id_conflict, + execute_actual_runtime_sequence, + persist_actual_runtime_partition, +) +from secure_eval_wrapper.live.shadow_repository import MemoryShadowRepository + +from phase8b_shadow_test_support import TEST_REPOSITORY_SHA + + +class Phase8BShadowCampaignVerifierRuntimeTests(unittest.TestCase): + def test_event_mutation_uses_real_persisted_decisions_and_diverges_at_suffix(self): + events = tuple(iter_shadow_campaign_events(stop_index=6)) + accounts = tuple( + synthetic_account_snapshot_for_event(event.event_index) + for event in events + ) + baseline = execute_actual_runtime_sequence( + events, accounts, repository_sha=TEST_REPOSITORY_SHA + ) + changed_ask = events[3].ask_price + events[3].tick_size + changed = replace( + events[3], + ask_price=changed_ask, + limit_price=(changed_ask if events[3].direction == "long" else events[3].limit_price), + ) + mutated_events = (*events[:3], changed, *events[4:]) + mutated = execute_actual_runtime_sequence( + mutated_events, accounts, repository_sha=TEST_REPOSITORY_SHA + ) + + self.assertEqual(baseline.row_count, 6) + self.assertEqual(mutated.row_count, 6) + self.assertEqual(baseline.event_sha256s[:3], mutated.event_sha256s[:3]) + self.assertEqual(baseline.input_sha256s[:3], mutated.input_sha256s[:3]) + self.assertEqual(baseline.decision_sha256s[:3], mutated.decision_sha256s[:3]) + self.assertEqual(baseline.chain_sha256s[:3], mutated.chain_sha256s[:3]) + self.assertNotEqual(baseline.event_sha256s[3], mutated.event_sha256s[3]) + self.assertNotEqual(baseline.input_sha256s[3], mutated.input_sha256s[3]) + self.assertNotEqual(baseline.decision_sha256s[3], mutated.decision_sha256s[3]) + self.assertTrue( + all( + left != right + for left, right in zip( + baseline.chain_sha256s[3:], mutated.chain_sha256s[3:] + ) + ) + ) + self.assertFalse(any(baseline.zero_authority_facts.values())) + self.assertFalse(any(mutated.zero_authority_facts.values())) + + def test_expected_metadata_cannot_change_actual_runtime_bundle(self): + event = next(iter_shadow_campaign_events(stop_index=1)) + snapshot = synthetic_account_snapshot_for_event(event.event_index) + changed_expectation = replace( + snapshot, expected_account_blockers=("expected_only",) + ) + self.assertEqual(snapshot.snapshot_sha256, changed_expectation.snapshot_sha256) + baseline = execute_actual_runtime_sequence( + (event,), (snapshot,), repository_sha=TEST_REPOSITORY_SHA + ) + mutated = execute_actual_runtime_sequence( + (event,), (changed_expectation,), repository_sha=TEST_REPOSITORY_SHA + ) + self.assertEqual(baseline.event_sha256s, mutated.event_sha256s) + self.assertEqual(baseline.account_sha256s, mutated.account_sha256s) + self.assertEqual(baseline.input_sha256s, mutated.input_sha256s) + self.assertEqual(baseline.decision_sha256s, mutated.decision_sha256s) + self.assertEqual(baseline.summary_sha256s, mutated.summary_sha256s) + self.assertEqual(baseline.bundle_sha256s, mutated.bundle_sha256s) + self.assertEqual(baseline.chain_sha256s, mutated.chain_sha256s) + + def test_repository_sha_subclasses_are_rejected_before_input_traversal(self): + class EvilSha(str): + callbacks = 0 + + def __eq__(self, other): + EvilSha.callbacks += 1 + raise AssertionError("forbidden repository SHA comparison executed") + + hostile = EvilSha(TEST_REPOSITORY_SHA) + calls = ( + lambda: execute_actual_runtime_sequence((), (), repository_sha=hostile), + lambda: compare_actual_runtime_streams((), (), repository_sha=hostile), + lambda: persist_actual_runtime_partition( + MemoryShadowRepository(), (), (), repository_sha=hostile + ), + lambda: execute_actual_same_run_id_conflict( + None, + None, + None, + None, + repository_sha=hostile, + ), + ) + for call in calls: + with self.subTest(call=call), self.assertRaises(TypeError): + call() + self.assertEqual(EvilSha.callbacks, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/open-core/tests/test_phase8b_shadow_postgres.py b/open-core/tests/test_phase8b_shadow_postgres.py index fa1d1eb..1d578bc 100644 --- a/open-core/tests/test_phase8b_shadow_postgres.py +++ b/open-core/tests/test_phase8b_shadow_postgres.py @@ -613,7 +613,11 @@ def test_committed_forged_rows_fail_closed_without_repair_or_overwrite(self): fresh = self.connect("primary") try: with self.assertRaises( - (ShadowBundleValidationError, PermissionError) + ( + ShadowBundleValidationError, + ShadowPersistenceConflict, + PermissionError, + ) ): PostgresShadowRepository( fresh, From 34b8c2863d1a0da1a0008c68882a238478c08397 Mon Sep 17 00:00:00 2001 From: Tcx086 <957658986chen@gmail.com> Date: Sun, 26 Jul 2026 11:49:06 -0400 Subject: [PATCH 2/2] docs: add Phase 8B historical campaign evidence --- ...e8b_historical_shadow_campaign_public.json | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/evidence/phase8b_historical_shadow_campaign_public.json diff --git a/docs/evidence/phase8b_historical_shadow_campaign_public.json b/docs/evidence/phase8b_historical_shadow_campaign_public.json new file mode 100644 index 0000000..c9fba52 --- /dev/null +++ b/docs/evidence/phase8b_historical_shadow_campaign_public.json @@ -0,0 +1,87 @@ +{ + "schema_version": 1, + "operation": "phase8b_historical_shadow_campaign", + "status": "implemented_pending_independent_audit", + "repository_sha": "8bb9dfee92d9fcc6f150a4b44057301640e2300c", + "accepted_shadow_baseline_merge_sha": "376aa35e5b8b77d67b24efca5bbc9fffc95137a5", + "campaign_version": "phase8b-historical-shadow-campaign-v1", + "verifier_version": "phase8b-historical-shadow-campaign-verifier-v1", + "verifier_result_sha256": "d7695aa1c8ffc7a91108ffe064cc700579a977003010d23fbb0d84201d46f044", + "corpus_generator_version": "phase8b-historical-shadow-corpus-generator-v1", + "corpus_spec_sha256": "2cc12c280c9a2c7bf59f118b77dded82498eb736bfa2a4ddaba8a97349ba9c0f", + "corpus_generator_sha256": "eb6161e005beba49abab0413781a59d1ce3491a6e7b4191cda2a67350fe7374f", + "synthetic_account_timeline_sha256": "6984f7a02e809aa13e2a3c2b733d111aa6a86babaa9786012637db428b6c3a16", + "timeframe": "5m", + "equivalent_duration_days": 90, + "event_count": 25920, + "segment_count": 12, + "segment_result_hashes": { + "full-90d-segment-01-quiet-range": "a1aa5895cf1b3395c91aa2ac5a38a01fe1ba635661a359c7722716e80c56b629", + "full-90d-segment-02-low-volatility-uptrend": "85d9065538aa5dfa9e9322e2cd8f241795d1c87805b067492db94b5754a47173", + "full-90d-segment-03-low-volatility-downtrend": "24b939b0d34bd2670b1000f8e050fedde1beb79141951492c5a8366f164d820d", + "full-90d-segment-04-high-volatility-uptrend": "ba305e84e676c5698b4f4cf817d14534a5aad7296eacf0f1d7dca47694313b11", + "full-90d-segment-05-high-volatility-downtrend": "b1ebab5cdc6f5564897341e92d04d43bd6fece085ee9c4a4109b6da83c699844", + "full-90d-segment-06-volatility-spike": "a56dc01f0226f6e615ed7fd5aae0e441166bfe99f10dd80ea6ea449d42ea25e6", + "full-90d-segment-07-flash-drawdown-and-bounded-rebound": "52d42bc1b798533dfca7ef12602f3f2bd729edf1ceaca62c0f9b1e33aee0bdda", + "full-90d-segment-08-deterministic-gap-sequence": "4509b532cb5768aef1e16a5875b9d4ed72ae796525d82332b1eb06aab3f33f75", + "full-90d-segment-09-illiquid-wide-spread-conditions": "e0cc09929fb6908713600d707d9a89a660770e91c4b9393a695cdd9570b0a261", + "full-90d-segment-10-stale-missing-duplicate-out-of-order-sequence": "026fe6f62b61ca88e26c57c3ea011d96e1666c10c1b4930724e5ee7446454bea", + "full-90d-segment-11-instrument-metadata-boundary-churn": "6f09ba034e75ad81ad93a8c8d197241ceb94dfa94a393d5680cffe0b73713754", + "full-90d-segment-12-recovery-and-normalization": "1448ae346e6f3cdfc21e288e6e629cb18d98e4f7cb763f24cc88f5c1a2e769e5" + }, + "window_result_hashes": { + "1d": "424c644ca52fe7447f228f7fcfdd2c9d167d6fa0ed23c201b3b32968170f8136", + "7d": "451a186cfd74868f7a707632ca95f08df57118da76839970587e05eec10fb286", + "30d": "41bb739bc5adfd375820a1740561f7abd42e237675220129fadda393f1e69fbe", + "90d": "6b149ca98a882fcd7e287f99b096cd09aae8000662041fce9d4375dec2e686ae" + }, + "accepted_decision_count": 5400, + "blocked_decision_count": 20520, + "shadow_intent_count": 23698, + "blocker_frequencies": { + "approval_notional": 4285, + "duplicate_public_response_rows": 7, + "excessive_reserved_notional": 2160, + "instrument_not_live": 36, + "insufficient_base_balance": 1080, + "insufficient_quote_balance": 1080, + "kill_switch_not_armed": 2124, + "maximum_daily_realized_loss": 2134, + "maximum_drawdown": 2134, + "maximum_gross_exposure": 2160, + "maximum_net_exposure": 2160, + "maximum_order_notional": 2125, + "maximum_position_notional_or_spot_short": 12912, + "partial_public_response": 9, + "stale_market_data": 15 + }, + "restart_cases_passed": 14, + "replay_cases_passed": 1, + "mutation_cases_passed": 8, + "anti_lookahead_cases_passed": 6, + "concurrency_cases_passed": 10, + "runtime_crash_cases_passed": 9, + "campaign_crash_cases_passed": 10, + "corrupted_bundle_rejections": 1, + "final_decision_chain_sha256": "520545db5d1f8000fb07475aac88405cf0e8419df066d6429a2a011585be464a", + "campaign_result_sha256": "e07a8d7d67f928ff1d0a4e29143a89a82f7d6e2511b84e0961e24a8802ee5741", + "postgresql_campaign_classification": "POSTGRESQL_CAMPAIGN_NOT_EXECUTED_DURING_ARTIFACT_GENERATION", + "postgresql_campaign_event_count": 0, + "public_network_smoke_executed": false, + "operator_bootstrap_executed": false, + "authenticated_proof_executed": false, + "real_proof_authorization": "NO", + "network_write_count": 0, + "production_transport_call_count": 0, + "authenticated_endpoint_call_count": 0, + "credential_read_count": 0, + "production_write_count": 0, + "production_submit_reachable": false, + "production_cancel_reachable": false, + "migration_count": 26, + "latest_migration": "0026", + "migration_0026_sha256": "698772fb68c5c4981256682d064c3be641193ab10c8dbf55e1a5b390ca7c504a", + "migration_0027_exists": false, + "independent_audit_status": "pending", + "evidence_payload_sha256": "0b0e0e8730f93c7ef7821571ca87756fdcdad12c34414b78445d1faadeabeed5" +}