diff --git a/tests/test_paper_admission.py b/tests/test_paper_admission.py index e520ee9..19f24a1 100644 --- a/tests/test_paper_admission.py +++ b/tests/test_paper_admission.py @@ -4,7 +4,7 @@ import pytest -from alpaca_platform import paper_admission +from alpaca_platform import paper_admission, shadow_ledger def sha(character: str) -> str: @@ -68,6 +68,23 @@ def test_non_paper_environment_is_rejected(environment: str): paper_admission.validate_paper_admission(admission) +def test_shadow_ledger_schema_is_not_paper_admission(): + admission = valid_admission() + admission["schema"] = shadow_ledger.RECEIPT_SCHEMA + admission["admission_sha256"] = paper_admission.calculate_paper_admission_sha256(admission) + + with pytest.raises(paper_admission.PaperAdmissionError, match="schema is unsupported"): + paper_admission.validate_paper_admission(admission) + + +def test_missing_paper_provenance_is_rejected(): + admission = valid_admission() + del admission["environment"] + + with pytest.raises(paper_admission.PaperAdmissionError, match="missing required field"): + paper_admission.validate_paper_admission(admission) + + @pytest.mark.parametrize("forbidden_field", ["shadow_receipt", "account_id", "order_id", "credential"]) def test_shadow_and_sensitive_material_cannot_be_added(forbidden_field: str): admission = valid_admission() diff --git a/tests/test_shadow_ledger.py b/tests/test_shadow_ledger.py index 9f2a580..c8fdd92 100644 --- a/tests/test_shadow_ledger.py +++ b/tests/test_shadow_ledger.py @@ -8,7 +8,7 @@ import pytest -from alpaca_platform import shadow_ledger +from alpaca_platform import paper_admission, shadow_ledger def sha(character: str) -> str: @@ -134,6 +134,33 @@ def test_genesis_receipt_is_chain_linked_and_contains_only_virtual_weight_change assert forbidden not in rendered.lower() +def test_paper_admission_is_not_shadow_input_or_receipt(): + admission = paper_admission.build_paper_admission( + environment="PAPER_DRY_RUN", + cycle_id="alpaca_paper_20260904_001", + valid_from="2026-09-04T00:00:00Z", + valid_until="2026-09-05T00:00:00Z", + paper_endpoint_sha256=sha("1"), + config_sha256=sha("2"), + strategy_sha256=sha("3"), + deployment_sha256=sha("4"), + risk_sha256=sha("5"), + ) + + with pytest.raises(shadow_ledger.ShadowLedgerError): + shadow_ledger.validate_shadow_cycle_input(admission) + with pytest.raises(shadow_ledger.ShadowLedgerError): + shadow_ledger.validate_shadow_ledger_receipt(admission) + + +def test_missing_shadow_lane_provenance_is_rejected(): + payload = cycle_input() + del payload["risk_control"] + + with pytest.raises(shadow_ledger.ShadowLedgerError, match="missing required field"): + shadow_ledger.validate_shadow_cycle_input(payload) + + def test_next_session_links_to_previous_virtual_allocation_without_broker_state(): first = shadow_ledger.build_shadow_ledger_receipt(cycle_input()) second = shadow_ledger.build_shadow_ledger_receipt( diff --git a/tests/test_shadow_receipt_store.py b/tests/test_shadow_receipt_store.py index 285569d..1aac53f 100644 --- a/tests/test_shadow_receipt_store.py +++ b/tests/test_shadow_receipt_store.py @@ -191,6 +191,18 @@ def test_ready_controller_outcome_becomes_a_closed_sanitized_admission(): shadow_receipt_store.validate_shadow_receipt_admission(tampered) +def test_scheduler_receipt_digest_mismatch_is_rejected_before_admission(): + outcome = ready_outcome() + mismatched = copy.deepcopy(outcome) + mismatched.result["shadow_receipt_sha256"] = sha("0") + + with pytest.raises(shadow_receipt_store.ShadowReceiptStoreError, match="bind ledger receipt digest"): + shadow_receipt_store.build_shadow_receipt_admission( + mismatched, + risk_gate_decision=risk_gate_decision_envelope(mismatched), + ) + + def test_in_memory_store_is_create_only_and_reconciles_identical_admission(): store = shadow_receipt_store.InMemoryShadowReceiptStore() outcome = ready_outcome()