From docs/INVARIANTS.md in the repository.
Invariants → the tests that prove them
The nine invariants in CLAUDE.md are enforced by code and proven by tests. Each entry names at least one test as path::test_name. tests/test_docs.py fails if any named test disappears or if an invariant loses its last test.
Run them all:
make test # everything in-process (integration tests skip)
make up && uv run pytest -m integration # the live end-to-end test1. No ambient authority
The agent never holds a bank credential. Only the enforcement point's secret broker does, and only after a valid grant.
tests/test_no_ambient_credential.py::test_sources_never_mention_the_broker_secret_or_token_mintingtests/test_no_ambient_credential.py::test_only_pep_and_bank_read_the_secrettests/test_no_ambient_credential.py::test_gateway_and_cli_processes_cannot_mint_a_tokentests/test_no_ambient_credential.py::test_gateway_compose_environment_carries_no_secretservices/gateway/tests/test_tool_forwarding.py::test_gateway_never_sees_a_bank_token_or_the_bankservices/pep/tests/test_execute.py::test_no_bank_token_in_responses_or_eventsplugins/treasury/mock/bank/tests/test_bank.py::test_missing_token_rejected
2. Classification is not authorization
Model output never widens scope or mints a grant. A fooled model can propose anything; only the deterministic policy decides.
tests/test_beneficiary_attack.py::test_injected_proposal_is_denied_beneficiary_not_approvedtests/test_beneficiary_attack.py::test_no_grant_and_no_transfer_to_the_injected_accountservices/gateway/tests/test_tool_forwarding.py::test_injected_beneficiary_is_denied_by_policyservices/authority/tests/test_policy.py::test_facade_not_in_scopeservices/authority/tests/test_policy.py::test_beneficiary_not_approved_when_account_does_not_match_approved_id
3. AI never creates authority
The Authority Engine is deterministic and makes no model call.
services/authority/tests/test_policy.py::test_anthropic_not_importable_via_authority_packageservices/authority/tests/test_policy.py::test_same_inputs_same_outputservices/authority/tests/test_policy.py::test_every_reason_code_is_reachableservices/authority/tests/test_policy.py::test_first_failure_wins_in_spec_order
4. Complete mediation for covered actions
bank.executeTransfer is reachable only through the enforcement point. The demo-bypass path exists only to show what happens without Situs, and it is labeled.
services/pep/tests/test_execute.py::test_modified_action_is_action_hash_mismatch_and_nothing_movesservices/pep/tests/test_execute.py::test_swapping_the_beneficiary_account_is_action_hash_mismatchservices/pep/tests/test_execute.py::test_forged_status_fails_the_authority_signatureservices/pep/tests/test_egress_allowlist.py::test_non_allowlisted_url_is_egress_deniedtests/test_bypass_documented.py::test_bypass_executes_the_injected_wire_and_prints_the_bannertests/test_bypass_documented.py::test_bypass_is_labeled_everywhere_it_appearstests/test_bypass_documented.py::test_nothing_but_the_bypass_flag_imports_the_bypass
5. Discovered credentials are data
A credential string in a document or in model context is never used.
tests/test_discovered_credentials.py::test_credential_as_a_tool_argument_is_refusedtests/test_discovered_credentials.py::test_credential_in_document_text_is_carried_as_data_and_never_presentedtests/test_discovered_credentials.py::test_credential_in_model_context_reaches_no_one
6. Single-use Action Grants with an idempotency key
The bank enforces idempotency.
tests/test_retry_storm.py::test_three_retries_all_duplicate_with_the_same_transfertests/test_retry_storm.py::test_exactly_one_transfer_at_the_bankservices/authority/tests/test_grant_lifecycle.py::test_lifecycle_pending_approved_active_redeemedservices/pep/tests/test_execute.py::test_reuse_is_grant_already_redeemed_and_one_transfer_existsplugins/treasury/mock/bank/tests/test_bank.py::test_idempotent_retry_three_posts_one_transferplugins/treasury/mock/bank/tests/test_bank.py::test_idempotency_key_bound_to_one_action
7. Revocation is first-class
Revoking an Intent Contract makes later grants impossible. The receipt shows the status as of verification time.
tests/test_revocation.py::test_next_proposal_is_denied_contract_revokedtests/test_revocation.py::test_receipt_status_reflects_the_revocationservices/authority/tests/test_policy.py::test_contract_revokedservices/pep/tests/test_execute.py::test_revoked_contract_is_refusedtests/test_tamper.py::test_as_of_later_than_revocation_check_prints_the_staleness_warning
8. Each fact is signed by the party responsible for it
Principal over the Intent Contract, approver over the action hash, enforcement point over the decision, counterparty over the acknowledgment (receipt 0.3: signatures.counterparty, kid from the plugin's connector.json; the demo bank for treasury). The acknowledgment is bound to the action hash and the grant's idempotency key inside the counterparty's own signed body, and the verifier's check 6 requires both. Situs signs only the assembly.
services/authority/tests/test_receipts.py::test_executed_transfer_receipt_has_all_five_signaturestests/test_legit_transfer_end_to_end.py::test_executed_receipt_carries_five_signatures_from_the_responsible_partiestests/test_demo_storyline.py::test_tamper_fails_at_assembly_then_at_the_approver_checktests/test_tamper.py::test_flipped_amount_fails_at_check_4tests/test_tamper.py::test_flipped_counterparty_ack_fails_at_check_6tests/test_tamper.py::test_an_ack_with_the_wrong_action_hash_fails_the_counterparty_checktests/test_tamper.py::test_an_ack_with_another_idempotency_key_fails_the_counterparty_checkservices/pep/tests/test_connector.py::test_an_ack_naming_another_action_is_recorded_then_refusedtests/test_tamper.py::test_tampered_contract_fails_at_check_3tests/test_hardware_keys.py::test_storyline_signed_with_touchid_under_the_hardware_policytests/test_hardware_keys.py::test_iphone_passkey_approves_through_the_consoletests/test_hardware_keys.py::test_passkey_signature_over_another_hash_is_refused_and_nothing_changes
9. Every receipt states what is proven and what is not
The attestation slot is L1-sim in this build. The hardware quote fields are present and empty.
tests/test_receipt_disclosure.py::test_every_receipt_has_non_empty_proven_and_unproventests/test_receipt_disclosure.py::test_attestation_is_l1_sim_and_hardware_quotes_are_present_and_emptytests/test_receipt_disclosure.py::test_unproven_names_the_missing_hardware_attestationservices/console/tests/test_console_pages.py::test_receipt_detail_is_who_what_where_how_much_status_with_proven_and_unproventests/test_proof_report.py::test_executed_receipt_grades_honestlytests/test_proof_report.py::test_bindings_hold_and_the_model_link_is_flagged_as_unbound
10. Plugin neutrality
Nothing under situs_core/ (except situs_core/plugins/ and the bundles), services/authority/, services/pep/, services/gateway/, services/console/ or cli/ names a plugin's domain. Every plugin declares a vocabulary; a plugin never imports from services/; core never imports a specific plugin. The enforcement point loads a plugin's declarations only (façades, profile, connector.json) and runs no plugin code.
tests/test_core_neutral.py::test_core_paths_carry_no_plugin_vocabularytests/test_core_neutral.py::test_core_never_imports_a_specific_plugintests/test_core_neutral.py::test_the_pep_and_the_receipt_core_are_already_neutralservices/pep/tests/test_connector.py::test_the_pep_executes_the_covered_facade_with_only_connector_jsonservices/pep/tests/test_connector.py::test_a_fresh_pep_process_imports_no_plugin_moduletests/test_core_neutral.py::test_a_glossary_word_is_allowed_bare_and_in_its_listed_compounds_onlytests/test_core_neutral.py::test_every_listed_glossary_compound_still_occurs_in_coretests/test_core_neutral.py::test_the_glossary_changes_nothing_for_a_word_outside_it
Core's own words (Task 34, docs/DECISIONS.md D34): a vocabulary word core also uses in a sense of its own (record, release, purpose, recipient; situs_core/plugins/glossary.py) may appear bare, and joined to other words only in the compounds the glossary lists. Every other vocabulary word is scanned as before.
11. Plugin narrowing
If core denies, the outcome is deny whatever the plugin says. A plugin decides deny, escalate or pass. The covered façade needs approval unless the signed profile envelope sets approval.delegate_to_plugin: true, and the receipt records that delegation.
tests/test_narrowing.py::test_core_deny_stays_deny_under_every_plugintests/test_narrowing.py::test_covered_facade_requires_approval_without_delegationtests/test_narrowing.py::test_delegation_is_recorded_in_the_receipttests/test_narrowing.py::test_core_deny_stays_deny_under_records
12. Approval assurance
An approval is accepted only if its signature verifies against a key enrolled for that approver at an assurance level at least as high as the route demands (software, hardware, hardware_attested). The receipt records the level achieved. No app, channel or console can lower it.
tests/test_approval_api.py::test_software_key_on_a_hardware_route_is_assurance_insufficienttests/test_approval_api.py::test_receipt_records_assurance_basis_and_app_idtests/test_approval_api.py::test_an_es256_key_enrolled_as_hardware_passes_a_hardware_route_as_declaredtests/test_approval_api.py::test_an_app_attest_attestation_with_a_bad_nonce_is_attestation_invalidtests/test_approval_api.py::test_the_console_and_the_mac_dialog_carry_decisions_as_registered_appstests/test_records_e2e.py::test_the_approval_api_refuses_a_software_key_on_the_hardware_route