Skip to content
Menu

Documentation

Guarantees & capabilities ledger

Every guarantee Sandloom promises and every capability a user can rely on, with the tests, sandloom check rules and import contracts that prove it. The ledger (guarantees.yml) is a CI gate (tests/architecture/test_guarantee_ledger.py): an enforcing test that disappears, an entry deleted without a removed: record, or a new ADR section without an entry fails the build.

429 guarantees, 174 capabilities, 37 recorded gaps.

Run exactly the enforcing tests (from the repository root):

uv run pytest -m guarantees src

Guarantees

G-TENANT

Id Statement Enforced by
G-TENANT-01 An ORM read, update or delete against a TenantScoped model with no tenant bound (and not inside unscoped()) raises MissingTenantError instead of running cross-tenant. sandloom/core/tests/test_tenant_isolation.py::test_no_tenant_bound_fails_closed
sandloom/core/tests/test_tenant_criteria_prebuilt.py::test_no_tenant_fails_closed
sandloom/core/tests/test_tenant_guard_early_exit.py::test_tenant_statements_are_still_scoped_and_fail_closed
G-TENANT-02 Once a tenant is bound, ORM SELECT, bulk UPDATE and bulk DELETE on a TenantScoped model are automatically filtered to that tenant's rows only. sandloom/core/tests/test_tenant_isolation.py::test_automatic_filtering_to_bound_tenant
sandloom/core/tests/test_tenant_isolation.py::test_bulk_update_scoped_to_bound_tenant
sandloom/core/tests/test_tenant_isolation.py::test_bulk_delete_scoped_to_bound_tenant
G-TENANT-03 The ORM guard also covers relationship loads (lazy, selectin, joined), joins, EXISTS/IN subqueries, select_from and Session.get (an identity-map hit is re-checked against the bound tenant): unbound they fail closed, bound they return only the bound tenant's rows. sandloom/core/tests/test_tenant_guard_gaps.py::test_unbound_reach_into_tenant_table_fails_closed
sandloom/core/tests/test_tenant_guard_gaps.py::test_unbound_lazy_relationship_load_fails_closed
sandloom/core/tests/test_tenant_guard_gaps.py::test_bound_relationship_loads_are_scoped
sandloom/core/tests/test_tenant_guard_gaps.py::test_session_get_identity_map_hit_is_rechecked
G-TENANT-04 An ORM object insert of a TenantScoped model inherits the bound tenant, fails with MissingTenantError when none is bound, and fails with TenantMismatchError when it names a different tenant_id. sandloom/core/tests/test_tenant_isolation.py::test_insert_inherits_bound_tenant
sandloom/core/tests/test_tenant_isolation.py::test_insert_without_tenant_fails_closed
sandloom/core/tests/test_tenant_isolation.py::test_insert_cannot_target_another_tenant
G-TENANT-05 Under a bound tenant, bulk INSERTs, bulk UPDATEs that change tenant_id (values or ordered_values) and bulk-by-primary-key UPDATEs on a TenantScoped model raise UnsafeTenantOperationError; they are only allowed inside explicit unscoped(). sandloom/core/tests/test_tenant_isolation.py::test_bulk_insert_requires_explicit_system_scope
sandloom/core/tests/test_tenant_isolation.py::test_bulk_update_cannot_change_tenant_id
sandloom/core/tests/test_tenant_guard_gaps.py::test_bulk_update_by_primary_key_is_refused
sandloom/core/tests/test_tenant_guard_gaps.py::test_ordered_values_cannot_change_tenant_id
G-TENANT-06 Core select/insert/update/delete statements (including a raw Table alias mixed into an ORM query) against a recognised TenantScoped table under a bound tenant are refused with UnsafeTenantOperationError when they cannot be safely scoped. sandloom/core/tests/test_infra_review_20261022.py::test_core_tenant_table_statements_cannot_escape_bound_scope
sandloom/core/tests/test_infra_review_20261022.py::test_mixed_orm_and_core_alias_cannot_read_another_tenant
G-TENANT-07 The explicit helpers scoped(), tenant_filter() and current_tenant_id() raise MissingTenantError when no tenant is bound, and scoped() restricts results to the bound tenant. sandloom/core/tests/test_tenant_isolation.py::test_explicit_scoped_is_fail_closed
sandloom/core/tests/test_tenant_isolation.py::test_single_tenant_runtime_scope_binds_canonical_tenant
sandloom/core/tests/test_ledger_runtime_pins.py::test_tenant_filter_fails_closed_and_binds_the_current_tenant
G-TENANT-08 Tenant scoping state never leaks between requests: the compiled-statement cache does not capture a tenant, a pooled connection never carries a previous request's tenant criteria, and declaring a new TenantScoped model invalidates the guard's memoised table set. sandloom/core/tests/test_tenant_criteria_prebuilt.py::test_compiled_cache_does_not_capture_the_tenant
sandloom/core/tests/test_tenant_criteria_prebuilt.py::test_previous_requests_tenant_never_leaks_on_one_connection
sandloom/core/tests/test_tenant_guard_cache.py::test_new_tenant_model_invalidates_caches
G-TENANT-09 Queries that touch no TenantScoped table run normally with no tenant bound (the guard fails closed only on tenant tables). sandloom/core/tests/test_tenant_guard_gaps.py::test_non_tenant_query_without_tenant_still_works
sandloom/core/tests/test_tenant_guard_early_exit.py::test_bound_tenant_non_tenant_statement_gets_no_criteria
G-TENANT-10 TenantScope binds the tenant only from the signed access token: an X-Tenant-Id header naming a tenant outside the token's tenant_id/org_id claim or tenant_ids allowlist is refused with 403, and a header inside the allowlist selects that tenant. sandloom/core/tests/test_tenant_isolation.py::test_header_cannot_override_token_tenant
sandloom/core/tests/test_tenant_isolation.py::test_header_can_select_tenant_from_token_allowlist
sandloom/core/tests/test_tenant_isolation.py::test_tenant_ids_parse_is_memoised_per_verified_token
G-TENANT-11 A signed tenant_ids claim containing a malformed entry fails every request with 400 'invalid tenant id' (never memoised, no string-membership shortcut). sandloom/core/tests/test_tenant_isolation.py::test_malformed_signed_tenant_ids_entry_is_400_on_every_request
sandloom/core/tests/test_tenant_isolation.py::test_tenant_ids_memo_is_bounded
G-TENANT-12 A required TenantScope with no resolvable tenant answers 400 'tenant context required', a token granting several tenants without an X-Tenant-Id selector answers 400 instead of picking one, and a MissingTenantError reaching the request boundary also answers 400 'tenant context required' (never a bare 500, never the internal message). tests/integration/test_request_boundaries.py::test_a_missing_tenant_at_the_boundary_is_the_tenant_scope_400
tests/integration/test_request_boundaries.py::test_tenant_scope_without_a_tenant_answers_400
tests/integration/test_request_boundaries.py::test_a_token_granting_several_tenants_needs_the_selector
G-TENANT-13 TenantScope rejects a revoked access token (401) before selecting a tenant from its claims, so a stale tenant_ids grant cannot keep selecting a tenant after revocation. sandloom/core/tests/test_auth_rotation_revocation.py::test_revoked_token_is_rejected_by_every_framework_guard
sandloom/core/tests/test_auth_rotation_revocation.py::test_idempotency_and_tenant_scope_ignore_non_access_bearers
G-TENANT-14 With TENANCY_MODE=single, settings refuse to load without SINGLE_TENANT_ID, TenantScope binds the canonical tenant, and an X-Tenant-Id selector or token tenant naming another tenant is refused with 403. sandloom/core/tests/test_tenant_isolation.py::test_single_tenant_mode_requires_canonical_tenant
sandloom/core/tests/test_tenant_isolation.py::test_single_tenant_mode_binds_canonical_tenant
sandloom/core/tests/test_tenant_isolation.py::test_single_tenant_mode_rejects_other_tenant_selector
gap (S): The 'token tenant does not match the configured tenant' 403 (token claim, no header) branch has no test.
G-TENANT-15 With TENANCY_MODE=single, request UoWs, worker/script UoWs and contract read scopes bind the canonical tenant automatically, and only for their own extent. sandloom/core/tests/test_tenant_isolation.py::test_single_tenant_runtime_scope_binds_canonical_tenant
sandloom/core/tests/test_open_uow.py::test_single_tenant_scope_bound_only_inside
tests/integration/test_planned_reads_tenancy_mode.py::test_read_scopes_bind_the_configured_tenant_like_the_uow
G-TENANT-16 sandloom check rule tenant-scoped-models reports every ORM model or Core Table declaring its own tenant_id without TenantScoped, unless listed in tenant_scope_exempt with a written reason; a stale exemption or one without a reason fails. tests/architecture/test_tenant_scoped_models_rule.py::test_fixture_violations_detected
tests/architecture/test_tenant_scoped_models_rule.py::test_rule_runs_in_sandloom_check_and_honours_exemptions
tests/architecture/test_tenant_scoped_models_rule.py::test_exemption_requires_a_reason
rule tenant-scoped-models
G-TENANT-17 sandloom check rule unsafe-tenant-sql reports runtime raw SQL in business modules (text(), Model.table, exec_driver_sql, including deferred lambdas/generators) unless it executes inside an explicit unscoped() block; declaration-time defaults are exempt. sandloom/core/tests/test_review_archcheck.py::test_raw_tenant_sql_requires_explicit_system_scope
sandloom/core/tests/test_review_archcheck.py::test_deferred_sql_does_not_inherit_creation_scope
sandloom/core/tests/test_review_archcheck.py::test_raw_sql_declaration_defaults_do_not_need_a_runtime_scope
tests/architecture/test_archcheck_library.py::test_framework_manifest_passes_every_rule
rule unsafe-tenant-sql
gap (S): The rule is only tested at the analyzer level (find_unsafe_tenant_sql); no test runs it through run_checks/sandloom check on a module fixture as the tenant-scoped-models rule is.
G-TENANT-18 monotonic_upsert requires a tenant_id in the row values, never overwrites another tenant's row with the same key, and raises TenantMismatchError when the row's tenant_id differs from the bound tenant; monotonic_update is scoped to the bound tenant. sandloom/core/tests/test_projection_upsert_guards.py::test_upsert_requires_tenant_and_never_crosses_tenants
sandloom/core/tests/test_projection_upsert_guards.py::test_update_is_scoped_to_bound_tenant

G-RLS

Id Statement Enforced by
G-RLS-01 On a tenant_rls database, every SQL path from the app role (ORM, Core via session/connection, text(), exec_driver_sql, streaming cursors, run_sync, executemany/insertmanyvalues, savepoints) sees and changes only rows of the tenant in sandloom.tenant_id, and an INSERT/UPDATE for another tenant is refused by WITH CHECK. sandloom/core/tests/test_tenant_rls.py::test_raw_text_sees_only_bound_tenant
sandloom/core/tests/test_tenant_rls.py::test_with_check_blocks_cross_tenant_writes
sandloom/core/tests/test_tenant_rls_paths.py::test_cross_tenant_insert_rejected_on_every_path
sandloom/core/tests/test_tenant_rls_paths.py::test_update_and_delete_see_only_bound_tenant
G-RLS-02 An unset, empty or malformed (not canonical lowercase UUID) sandloom.tenant_id yields zero rows and refused writes on an RLS table, never a cast error or fail-open, including when no engine hook is installed. sandloom/core/tests/test_tenant_rls_bypass.py::test_malformed_tenant_setting_fails_closed
sandloom/core/tests/test_tenant_rls_paths.py::test_no_tenant_fails_closed_for_every_operation
sandloom/core/tests/test_tenant_rls.py::test_policy_without_engine_hook_fails_closed
tests/perf_budgets/test_rls_merged_begin.py::test_malformed_tenant_in_context_sends_nothing
G-RLS-03 The RLS policy has no bypass: neither unscoped() nor the legacy sandloom.tenant_bypass setting lets the app role read or write another tenant's rows. sandloom/core/tests/test_tenant_rls_bypass.py::test_bypass_setting_cannot_read_other_tenants
sandloom/core/tests/test_tenant_rls_bypass.py::test_bypass_setting_cannot_write_other_tenants
sandloom/core/tests/test_tenant_rls_bypass.py::test_unscoped_never_reaches_other_tenants_on_app_role
sandloom/core/tests/test_tenant_rls_paths.py::test_legacy_bypass_setting_is_inert_on_every_path
G-RLS-04 sandloom.tenant_id is transaction-local and re-synced by the engine hook: it follows a tenant switch within a transaction, is restored after a savepoint rollback, and never carries one pooled transaction's tenant into the next. sandloom/core/tests/test_tenant_rls.py::test_settings_do_not_leak_across_pooled_transactions
sandloom/core/tests/test_tenant_rls.py::test_settings_resync_within_one_transaction
sandloom/core/tests/test_tenant_rls.py::test_savepoint_rollback_forces_resync
sandloom/core/tests/test_tenant_rls_bypass.py::test_crud_is_tenant_scoped_and_pool_reuse_does_not_leak
G-RLS-05 Startup refuses (TenantRLSConfigError, in every environment, aborting aopen_all and Celery worker boot) a tenant_rls database whose application role is a superuser, has BYPASSRLS, is a member of such a role, owns an RLS table directly or by membership, or is covered by an unconditional (system-role) policy. sandloom/core/tests/test_tenant_rls_bypass.py::test_verification_refuses_superuser_app_role
sandloom/core/tests/test_tenant_rls_bypass.py::test_verification_refuses_owner_and_member
sandloom/core/tests/test_tenant_rls_bypass.py::test_verification_failure_aborts_startup_in_every_environment
sandloom/core/tests/test_worker.py::test_worker_boot_runs_tenant_rls_role_verification
gap (S): The BYPASSRLS-attribute branch (and membership in a BYPASSRLS/superuser role) has no direct test; only SUPERUSER, table ownership and system-role membership are exercised.
G-RLS-06 The role check cannot be switched off: the former verify_roles database option is refused at startup, and a hardened NOSUPERUSER NOBYPASSRLS role passes. sandloom/core/tests/test_tenant_rls_bypass.py::test_removed_verify_roles_option_is_refused
sandloom/core/tests/test_tenant_rls_bypass.py::test_verification_accepts_hardened_roles
G-RLS-07 enable_tenant_rls(op, table) emits (through the Alembic op) ENABLE + FORCE row level security and the _tenant_isolation policy, is idempotent, and disable_tenant_rls reverts it; the tenant setting SQL is parameterised.
sandloom/core/tests/test_tenant_rls.py::test_migration_helper_emits_policy_through_op
sandloom/core/tests/test_tenant_rls.py::test_disable_reverts_policy
sandloom/core/tests/test_tenant_rls.py::test_install_is_idempotent_and_postgres_only
sandloom/core/tests/test_tenant_rls.py::test_set_config_sql_is_parameterised
G-RLS-08 The system channel (system_session) reaches other tenants only on tables migrated with enable_tenant_rls(..., system_access=True), stays tenant-scoped on every other RLS table, honours an explicit tenant_id, rolls back on error, and writes an sandloom.system_db.session audit row with actor and reason in the same transaction. sandloom/core/tests/test_tenant_rls_bypass.py::test_system_channel_reaches_all_tenants_and_is_audited
sandloom/core/tests/test_tenant_rls_bypass.py::test_system_session_tenant_scoped_and_rollback
gap (S): 'No audit row, no commit' (a failing audit write aborts the system session's commit) and the structured start/end log record (op_id, outcome, duration) are not tested.
G-RLS-09 system_session refuses (SystemSessionRefused) an empty actor or a too-short reason and raises SystemChannelUnavailable for a database with no system channel; require_system_channel without a system DSN, or a system_url without tenant_rls, fails at provider construction. sandloom/core/tests/test_tenant_rls_bypass.py::test_system_session_requires_actor_reason_and_configuration
sandloom/core/tests/test_tenant_rls_bypass.py::test_require_system_channel_without_dsn_fails_fast
G-RLS-10 Business-module code has no path to the system channel: sandloom.core.drivers (system_db) and sandloom.core.sql_alchemy.tenant_rls are forbidden infra imports, absent from the public core API, and the database provider exposes no system engine; the app role cannot SET ROLE to the system role. tests/architecture/test_system_channel_unreachable.py::test_module_import_of_system_channel_is_flagged
tests/architecture/test_system_channel_unreachable.py::test_system_channel_not_in_public_core_api
tests/architecture/test_system_channel_unreachable.py::test_provider_does_not_expose_system_engine
sandloom/core/tests/test_tenant_rls_bypass.py::test_app_role_cannot_assume_system_role
rule infra-imports
G-RLS-11 A raw asyncpg driver connection bypasses the RLS hook but runs under the transaction's last synced setting, so with no tenant it fails closed (zero rows). sandloom/core/tests/test_tenant_rls_paths.py::test_raw_driver_connection_is_not_covered_but_fails_closed
G-RLS-12 The in-process contract pool installs the RLS hook too, so contract_session() statements are scoped by sandloom.tenant_id on a tenant_rls database (also when tenant_rls is requested after the default contract engine exists). sandloom/core/tests/test_contract_pool.py::test_default_contract_engine_honours_a_later_tenant_rls_request
G-RLS-13 The no-tenant round-trip-skipping allowlist never weakens RLS: it never skips a bound tenant, clears a tenant set earlier in the transaction, ends with its block, refuses untagged raw SQL or a tenant table inside it (NonTenantStatementError), and in production syncs instead of skipping on a violation. sandloom/core/tests/test_tenant_rls_allowlist.py::test_allowlist_never_skips_a_bound_tenant
sandloom/core/tests/test_tenant_rls_allowlist.py::test_a_tenant_table_in_the_scope_is_refused
sandloom/core/tests/test_tenant_rls_allowlist.py::test_untagged_raw_sql_in_the_scope_is_refused
sandloom/core/tests/test_tenant_rls_allowlist.py::test_in_production_a_violation_is_synced_never_skipped
G-RLS-14 Residual risk is pinned, not hidden: forging sandloom.tenant_id via set_config under the shared app role reads another tenant (strict xfail), and a superuser bypasses RLS (documented caveat test); a fix that changes either must flip these tests. sandloom/core/tests/test_tenant_rls_bypass.py::test_forged_tenant_setting_cannot_read_other_tenant
sandloom/core/tests/test_tenant_rls.py::test_superuser_bypasses_rls_documented_caveat
G-RLS-15 A database instance without tenant_rls is unchanged: no hook, no system channel, no I/O at aopen, and the provider does not advertise the tenant_rls feature (it is advertised only when the option is on). sandloom/core/tests/test_tenant_rls_bypass.py::test_rls_off_instance_is_unchanged
sandloom/core/tests/test_tenant_rls_paths.py::test_tenant_rls_off_changes_nothing
sandloom/core/tests/test_database_tenant_rls_feature.py::test_provider_advertises_tenant_rls_only_when_enabled

G-PROFILE

Id Statement Enforced by
G-PROFILE-01 With ENVIRONMENT=prod and PRODUCTION_PROFILE=multi_tenant_strict, boot refuses (enforce_configuration) TENANCY_MODE other than multi, any database instance without tenant_rls, AUTH_REVOCATION_FAIL_CLOSED=false, and a SECURITY_CACHE_INSTANCE that is 'default', undeclared, or on the same Redis server as the default cache — listing every reason. sandloom/core/tests/test_production_profile.py::test_default_production_config_is_refused_with_every_reason
sandloom/core/tests/test_production_profile.py::test_each_trust_layer_is_required
G-PROFILE-02 The strict profile's boot check stops every entry point — ASGI app, Celery worker, sandloom migrate and the sandloom CLI — before it serves or touches anything. sandloom/core/tests/test_boot_configuration_checks.py::test_moved_validator_refuses_every_entry_point
G-PROFILE-03 Under the strict profile AUTH_REVOCATION_FAIL_CLOSED is turned on when unset, and a compliant configuration starts. sandloom/core/tests/test_production_profile.py::test_compliant_strict_profile_starts_and_forces_fail_closed
G-PROFILE-04 The strict profile is inert outside ENVIRONMENT=prod and with PRODUCTION_PROFILE=standard. sandloom/core/tests/test_production_profile.py::test_profile_is_inert_outside_prod_and_for_standard
G-PROFILE-05 Under the strict profile a critical startup task (API and worker) fails with ProductionProfileError, naming each table and the enable_tenant_rls(op, table) fix, when any TenantScoped table on any database lacks enabled+FORCEd RLS or its _tenant_isolation policy, or is missing.
sandloom/core/tests/test_production_profile.py::test_rls_coverage_reports_unprotected_tenant_tables
sandloom/core/tests/test_production_profile.py::test_startup_enforcement_fails_on_an_uncovered_table
gap (S): enforce_runtime is tested directly, but no test proves it is wired as a critical startup task of create_app's lifespan or of the Celery worker boot (removing either call site would stay green).
G-PROFILE-06 Under the strict profile startup attests via INFO server identity that the security cache (and a separate idempotency cache) reach a different Redis process than the default cache, failing with ProductionProfileError on the same process or when identity cannot be read. sandloom/core/tests/test_infra_review_20261022.py::test_strict_profile_checks_physical_redis_identity
sandloom/core/tests/test_idempotency_cache_instance.py::test_strict_runtime_attests_the_idempotency_redis_process
gap (S): The 'Cannot attest separate ... Redis processes' failure (deployment_identity raising) has no test.
G-PROFILE-07 Under the strict profile an IDEMPOTENCY_CACHE_INSTANCE set to another instance than the security cache must be declared and on a different Redis server than the default cache (refused otherwise), sharing the security Redis is only a warning, and leaving it unset keeps the previous behaviour. sandloom/core/tests/test_idempotency_cache_instance.py::test_strict_profile_refuses_an_unsafe_idempotency_instance
sandloom/core/tests/test_idempotency_cache_instance.py::test_strict_profile_warns_when_sharing_the_security_redis
sandloom/core/tests/test_idempotency_cache_instance.py::test_strict_profile_accepts_a_separate_idempotency_redis
sandloom/core/tests/test_idempotency_cache_instance.py::test_strict_profile_default_is_unchanged
G-PROFILE-08 Security-critical state (revocation marks, blocks, idempotency records) is written through SECURITY_CACHE_INSTANCE, never plain cache(); idempotency records move to IDEMPOTENCY_CACHE_INSTANCE when set while revocation marks stay in the security cache. sandloom/core/tests/test_production_profile.py::test_security_state_uses_the_security_cache_instance
sandloom/core/tests/test_idempotency_cache_instance.py::test_revocation_marks_stay_in_the_security_cache
sandloom/core/tests/test_idempotency_cache_instance.py::test_reserve_replay_conflict_release_use_the_idempotency_instance
G-PROFILE-09 Under the strict profile a split deployment (SERVICE_BINDINGS set) is refused with CONTRACT_COMPAT_CHECK=off or a shared HS secret (the accepted risk shared_hs_secret_split is refused), and a process loading no token issuer is refused an HS secret by the kernel role check with no opt-out. sandloom/core/tests/test_production_profile.py::test_each_trust_layer_is_required
sandloom/core/tests/test_production_profile.py::test_split_requires_asymmetric_jwt
sandloom/core/tests/test_production_profile.py::test_startup_role_check_has_no_shared_secret_opt_out_under_the_profile

G-UOW

Id Statement Enforced by
G-UOW-01 A Unit of Work commits when its body succeeds and rolls back (dispatching no outbox rows) when the body raises any exception, HTTPException included. sandloom/core/tests/test_open_uow.py::test_commit_then_dispatch
sandloom/core/tests/test_open_uow.py::test_body_error_rolls_back_without_dispatch
sandloom/core/tests/test_durable_idempotency_guard.py::test_rolled_back_request_leaves_no_record
sandloom/core/tests/test_fused_effect_rows.py::test_rolled_back_uow_writes_nothing
G-UOW-02 Every framework UoW dependency (DbSession, Database(name), get_db) is declared with scope="function", so the request transaction commits (and its on-commit hooks run) before the response is sent. sandloom/core/tests/test_uow_commits_before_response.py::test_dbsession_and_database_markers_use_function_scope
sandloom/core/tests/test_uow_commits_before_response.py::test_every_framework_get_db_dependency_declares_function_scope
sandloom/core/tests/test_uow_commits_before_response.py::test_db_session_dependency_exits_before_the_response_starts
G-UOW-03 Only the UnitOfWorkContract issues COMMIT and only the commit pipeline owners reach its stages; business modules never call db.commit()/db.rollback() (uow-boundary rule). tests/architecture/test_commit_pipeline.py::test_only_the_uow_contract_commits_an_async_session
tests/architecture/test_commit_pipeline.py::test_pipeline_stages_are_reached_only_through_their_owners
tests/architecture/test_no_uow_boundary_breaks.py::test_module_does_not_break_uow_boundary
tests/architecture/test_no_uow_boundary_breaks.py::test_analyzer_detects_boundary_break
rule uow-boundary
G-UOW-04 The commit pipeline runs in the documented order before_commit -> COMMIT -> outbox dispatch -> on_commit, and on_commit callbacks run after the outbox stage (also when there is no outbox stage). tests/architecture/test_commit_pipeline.py::test_the_pipeline_is_documented_in_order
sandloom/core/tests/test_open_uow.py::test_pipeline_runs_on_commit_after_the_outbox_stage
sandloom/core/tests/test_open_uow.py::test_pipeline_without_outbox_stage_still_runs_on_commit
sandloom/core/tests/test_before_commit.py::test_checks_run_in_order_after_the_handler_and_before_commit
G-UOW-05 Rolling back a SAVEPOINT (db.begin_nested()) discards the outbox rows, on_commit callbacks and before_commit checks registered inside it, while those of surviving savepoints and the parent stay live. sandloom/core/tests/test_savepoint_tracking.py::test_rolled_back_savepoint_kills_its_token_and_survivors_live
sandloom/core/tests/test_kernel_hardening.py::test_publish_in_rolled_back_savepoint_is_not_dispatched
sandloom/core/tests/test_fused_effect_rows.py::test_written_rows_of_a_rolled_back_savepoint_are_not_dispatched
sandloom/core/tests/test_on_commit.py::test_rolled_back_savepoint_drops_its_callbacks
G-UOW-06 When several database sessions are active in one scope, publish_event and record_audit without database= raise instead of guessing, and an explicit database= deterministically selects that database's UoW (each database owns its own outbox). sandloom/core/tests/test_review_fixes_v2.py::test_resolve_scoped_session_ambiguous_without_database
sandloom/core/tests/test_review_fixes_v2.py::test_resolve_scoped_session_single_database
gap (S): Only the private resolver is unit-tested; no end-to-end test holds DbSession + Database("x") in one endpoint and asserts the event lands in the chosen database's outbox_messages (and is marked delivered there).
G-UOW-07 Two named databases are independent engines with independent UoWs; there is no shared COMMIT across them (no distributed transaction). sandloom/core/tests/test_infra_multi_database.py::test_two_databases_are_independent_and_both_work
gap (S): Non-atomicity itself is a documented non-guarantee; no test pins that a failing second-database UoW leaves the first committed (i.e. that nobody quietly introduces a coupled commit).
G-UOW-08 The default-database UoW binds the single-tenant canonical tenant only inside the UoW and a tenant-scope failure on enter rolls the UoW back. sandloom/core/tests/test_open_uow.py::test_single_tenant_scope_bound_only_inside
sandloom/core/tests/test_open_uow.py::test_tenant_scope_failure_on_enter_rolls_back

G-UNIT

Id Statement Enforced by
G-UNIT-01 db() is the session of the innermost open unit of the current context; outside any unit it is SANDLOOM-R002; the first db() in a request creates the lazy request unit (handler task only), committed on success and rolled back on error before the response; a closed unit refuses reuse and never auto-begins (an exited unit's session becomes a ClosedKernelSession: every mutation and I/O entry refuses with SANDLOOM-R002, independent of DB_SESSION_OWNER_CHECKS). tests/integration/test_units_and_admission.py::test_db_outside_any_unit_is_refused
tests/integration/test_units_and_admission.py::test_db_in_a_block_is_that_units_session
tests/integration/test_units_and_admission.py::test_lazy_request_unit_commits_on_success_and_rolls_back_on_error
tests/integration/test_units_and_admission.py::test_child_db_before_the_parent_creates_nothing_then_parent_owns
tests/integration/test_units_and_admission.py::test_child_open_uow_before_the_parent_works
tests/integration/test_units_and_admission.py::test_an_exited_unit_refuses_reuse_and_never_autobegins
G-UNIT-02 Operation-intent mode: no request unit, sequential units; external_call() and release_check() exclude any unit, read-scope connection or checkout during their lifetime, the window opening atomically with the no-unit check and re-verified after the read-scope drain, so a unit a racing task opens meanwhile is refused (SANDLOOM-R008; the authority callback's read-contract calls excepted); the drain waits for a read contract already running on the scope's shared connection and closes its unit (a window entered from inside that read is SANDLOOM-R008); the window also waits for every contract call of the request already running in its provider unit (command or read; refused from inside one) and refuses a new one while open, so it never runs beside a provider transaction; a REMOTE contract call (the generated RPC proxy) obeys the same rules: refused inside a window before it is sent, counted while it runs so a window entered meanwhile waits for it (local == remote), and a remote call with an attempt its provider never answered (a timeout, a transport failure: its provider unit may still run after the transport gave up) refuses every later window of the request, fail closed (503, SANDLOOM-R008; the client retries with its Idempotency-Key), while an answered failure leaves it open, "answered" meaning its MAC-bound effects were merged (round 5): an answer whose fx the caller never merged - one larger than RPC_MAX_RESPONSE_BYTES (its fx.id lost with it), a reply without a well-formed envelope, malformed fx, or an unkeyed (unauthenticated) refusal - counts as unanswered (round 6: fx is malformed when any field is unknown or any ENTRY of rk / id / sr / ra / su has the wrong type or exceeds its bound - fx.id [None], a 17th id, an unknown risk, a non-positive step-up, an ra other than exactly the integer 1 (round 7: 1.0 and true are malformed) -; its well-formed risks still mark the request, its ra never does), so a root catching that 502 / 503 cannot open a window beside a nested unit that may still run; at any depth: a provider (or a local contract) whose own nested remote call went unanswered reports it back MAC-bound (fx.id) even when it caught the failure and answered OK, and every caller up the chain merges it, so the root's windows refuse too (a replay of that provider's command does not re-raise it: the uncertainty is not a committed effect); a 2xx outside the issue case (at least one external call, release_check after the last, exactly one write after it) or the issued-retry case (no commit after the release) is discarded with SANDLOOM-R012; an organization account needs the authority; db() is SANDLOOM-R002; SANDLOOM-B019 at startup. tests/integration/test_route_runtime.py::test_an_intent_2xx_without_release_check_is_discarded
tests/integration/test_route_runtime.py::test_two_writes_after_the_release_check_are_discarded
tests/integration/test_route_runtime.py::test_the_issue_and_issued_retry_cases_pass_and_hold_no_connection
tests/integration/test_route_runtime.py::test_release_check_for_an_org_needs_the_authority_and_db_is_refused
tests/integration/test_route_runtime.py::test_capability_replay_checks_at_startup
sandloom_billing/tests/test_operation_intents.py
sandloom_billing/tests/test_intent_reconcile.py
sandloom_billing/tests/test_f_class_authority.py
sandloom/core/tests/test_intent_window_race.py::test_a_unit_racing_the_window_entry_is_refused
sandloom/core/tests/test_intent_window_race.py::test_a_unit_left_open_by_the_drain_refuses_the_window
sandloom/core/tests/test_contract_read_scope.py::test_an_intent_window_waits_for_a_running_read_and_closes_its_unit
sandloom/core/tests/test_contract_read_scope.py::test_an_intent_window_inside_a_running_read_is_refused
sandloom/core/tests/test_contract_read_scope.py::test_an_intent_window_never_runs_beside_a_running_provider_unit
sandloom/core/tests/test_rpc_intent_window.py::test_a_remote_command_inside_an_intent_window_is_refused
sandloom/core/tests/test_rpc_intent_window.py::test_an_intent_window_waits_for_a_running_remote_command
tests/integration/test_rpc_caller_parity.py::test_no_window_opens_while_a_timed_out_remote_command_may_run
tests/integration/test_rpc_caller_parity.py::test_an_answered_remote_failure_leaves_the_window_open
tests/integration/test_rpc_caller_parity.py::test_a_nested_remote_timeout_keeps_the_root_in_doubt
tests/integration/test_rpc_caller_parity.py::test_a_local_wrapper_catching_a_remote_timeout_leaves_the_request_in_doubt
tests/integration/test_rpc_caller_parity.py::test_an_oversized_answer_keeps_the_nested_doubt
tests/integration/test_rpc_caller_parity.py::test_an_answer_whose_effects_were_not_merged_is_in_doubt
tests/integration/test_rpc_caller_parity.py::test_a_well_formed_answer_settles_the_call
G-UNIT-03 Write admission covers the cursor boundary: a write through the session's connection (conn.exec_driver_sql, also under unscoped()) is classified and refused like an ORM or Core write when another unit of the request is the writer. tests/integration/test_units_and_admission.py::test_a_second_writing_unit_is_refused_before_the_cursor
tests/integration/test_units_and_admission.py::test_classification_of_core_constructs

G-MSTORE

Id Statement Enforced by
G-MSTORE-01 The write store of a route is resolved statically: a single writable session is the write store; create_app refuses a write route with several writable sessions and neither writes() nor allow_multi_db_write, and a writes() naming a database the route does not inject (SANDLOOM-B009), and an allow_multi_db_write secondary that is the write store or not injected (SANDLOOM-B013). tests/integration/test_write_store.py::test_several_writable_sessions_need_a_write_store
G-MSTORE-02 A money-ledger insert in an opted-out (allow_multi_db_write) secondary is refused (SANDLOOM-B014); the same insert in the route's write store is admitted. tests/integration/test_write_store.py::test_a_money_effect_in_an_opted_out_secondary_is_refused
G-MSTORE-03 One writer per request: the first unit that writes becomes the writer; a write by any other unit of the request is refused before the cursor (SANDLOOM-R007), unflushed changes at COMMIT included; a read-only parent with a child's sole write is allowed, a child and its parent never both write; declared secondaries (allow_multi_db_write) are admitted, before or after the write store (a secondary is never the writer), and commit first, the write store last, whatever the endpoint's parameter order and however the sessions are nested in dependencies (either order, any depth, a session shared with the endpoint); a later commit failing after a writing unit of the request committed (a partial commit) increments sandloom_multi_store_partial_commit_total{route,store} (the route template, each committed store) and logs a warning naming the route, the committed stores and the failing store, while a request that commits everything or nothing is never counted; outside a request units are unconstrained. tests/integration/test_units_and_admission.py::test_a_second_writing_unit_is_refused_before_the_cursor
tests/integration/test_units_and_admission.py::test_an_unflushed_change_in_a_second_unit_is_refused_at_commit
tests/integration/test_units_and_admission.py::test_the_first_writer_wins_even_if_it_opened_later
tests/integration/test_units_and_admission.py::test_read_only_parent_and_a_childs_sole_write_is_allowed
tests/integration/test_units_and_admission.py::test_a_child_and_its_parent_never_both_write
tests/integration/test_units_and_admission.py::test_outside_a_request_units_are_unconstrained
tests/integration/test_units_and_admission.py::test_declared_secondaries_are_admitted
tests/integration/test_units_and_admission.py::test_a_secondary_written_before_the_write_store_is_admitted
tests/integration/test_route_runtime.py::test_the_secondary_commits_before_the_write_store_whatever_the_order
tests/integration/test_route_runtime.py::test_a_write_store_commit_failing_after_the_secondary_committed_is_counted
tests/integration/test_units_and_admission.py::test_random_unit_sequences_never_execute_a_second_writer
tests/laws/test_write_admission_laws.py::test_no_second_writer_statement_ever_executes
G-MSTORE-04 Write admission's stated scope: every SQLAlchemy statement through a kernel session or connection is classified (transaction-control statements are not writes; raw DBAPI is unavailable to modules); contract, consumer, task, saga-step and kernel units run in a fresh admission scope. tests/integration/test_units_and_admission.py::test_classification_of_core_constructs
tests/integration/test_units_and_admission.py::test_random_unit_sequences_never_execute_a_second_writer
tests/laws/test_write_admission_laws.py::test_no_second_writer_statement_ever_executes
G-MSTORE-05 create_app refuses (StoreCapabilityError, SANDLOOM-B010) a write route whose write store lacks audit while the route carries a static risk, a route with an explicit durable idempotency guard whose default database lacks http_idempotency, and any database hosting request units whose driver lacks write_admission; the check reads the registry's advertised features without building a provider. tests/integration/test_write_store.py::test_a_write_store_without_a_default_feature_is_refused_at_boot
tests/integration/test_write_store.py::test_a_database_hosting_request_units_needs_write_admission
G-MSTORE-06 create_app refuses (SANDLOOM-B011) a write route with a writable session on a store without transactions (it cannot commit with the request unit), unless that database is a declared allow_multi_db_write secondary. tests/integration/test_write_store.py::test_a_non_transactional_store_in_a_write_route_is_refused
G-MSTORE-07 Consistency tokens never become an error: SANDLOOM-Consistency-Token is HMAC-SHA256 under PROJECTION_TOKEN_KEYS (the newest kid signs, every listed kid verifies), expires after 600 s, carries at most 16 entries (a larger write gets none) and is ignored when missing, malformed, forged, expired, of an unknown kid or of another tenant; a write route whose pending events feed a projection answers with the token of the rows they produce, each entry naming its source group and aggregate; a read presenting it waits at most wait_ms (PROJECTION_RYW_WAIT_MS) until each entry's own group version column reaches it (never another group's version; a page read rereads its rows every poll round and a relational read reloads the rows its session already holds, so a cached row behind the token catches up within the budget), then serves with SANDLOOM-Projection-Stale: true (on_stale="serve") or reads the entity from its source (on_stale="source"). sandloom/core/tests/test_projection_kernel.py::test_a_token_round_trips_under_the_newest_kid_and_any_listed_kid
sandloom/core/tests/test_projection_kernel.py::test_a_bad_token_is_ignored_never_an_error
sandloom/core/tests/test_projection_kernel.py::test_a_large_write_gets_no_token_and_no_key_means_no_token
sandloom/core/tests/test_projection_kernel.py::test_a_read_waits_only_for_its_own_projections_missing_versions
tests/sdk/test_sdk_projection_ops.py::test_a_write_token_names_the_rows_its_events_produce
tests/sdk/test_sdk_projection_ops.py::test_a_write_route_sets_the_token_header
tests/sdk/test_sdk_projection_ops.py::test_a_read_waits_for_the_token_then_serves_stale
tests/sdk/test_sdk_projection_ops.py::test_the_token_digest_is_the_kernels
sandloom/core/tests/test_projection_kernel.py::test_a_wait_compares_each_source_group_with_its_own_version
tests/sdk/test_sdk_projection_ops.py::test_a_token_of_one_group_is_not_satisfied_by_another_groups_version
tests/sdk/test_sdk_projection_ops.py::test_a_store_token_compares_its_own_groups_version
tests/sdk/test_sdk_projection_ops.py::test_a_waiting_read_rereads_until_its_group_catches_up
tests/sdk/test_sdk_projection_ops.py::test_a_waiting_relational_read_reloads_the_rows_it_already_holds
G-MSTORE-08 Projection apply converges: full-state events replace a group's owned fields iff the stored version is NULL or older, so any interleaving with duplicates ends at the max-version event per group, on a relational model and on a conditional_write store alike (an independent group's upsert never clears another group's tombstone); every column has exactly one owning group (SANDLOOM-B012); delta mappers are accepted only for a relational projection under the durable inbox (exactly once by the inbox) and refused on a store target. tests/laws/test_projection_laws.py::test_full_state_converges_to_the_max_version_event_per_group
tests/laws/test_projection_laws.py::test_a_column_has_exactly_one_owner_or_the_projection_is_refused
tests/laws/test_projection_laws.py::test_a_delta_mapper_is_accepted_only_under_the_durable_inbox
tests/laws/test_projection_laws.py::test_deltas_under_the_inbox_count_each_event_exactly_once
tests/sdk/test_sdk_projections.py::test_full_state_converges_under_shuffles_and_duplicates
tests/sdk/test_sdk_projection_ops.py::test_a_store_projection_converges_and_reads_skip_tombstones
tests/sdk/test_sdk_projection_ops.py::test_a_store_target_refuses_deltas_and_two_targets
tests/sdk/test_sdk_projection_ops.py::test_an_independent_group_never_resurrects_a_store_tombstone
G-MSTORE-09 Tombstones are versioned and beat any late older upsert; only a newer write of the group that owns deletion clears one (never another group's upsert, on a relational model and a store target alike, as the conditional_write conformance kit checks); they are purged only by purge_tombstones(name, older_than=) / sandloom projection purge, which refuses an older_than below the replay horizon (stream retention + relay horizon, the oldest DLQ entry and the oldest dead outbox row of the source channels; never with an unbounded retention) and while any rebuild runs (running rebuilds are counted, so one finishing never lifts the exclusion of another; a rebuild's entry and a purge's rebuild check, watermark raise and delete are mutually exclusive - the purge window, a per-projection transaction advisory lock the relational begin_rebuild takes too, an asyncio lock in memory - so a rebuild that starts after the purge read no rebuild waits until the delete is done, and a purge after a begun rebuild is refused), raises the monotonic purged_before watermark before deleting (a rebuild never lowers it), and from then on the apply path dead-letters an event emitted before the watermark (fail closed without emitted_at); the apply''s watermark check and its write are atomic with respect to a purge (the apply''s unit holds the watermark row FOR SHARE, created when missing, until it commits; the raise row-locks it), so a purge racing an apply never resurrects a row, on a relational model and a store target alike. tests/laws/test_projection_laws.py::test_a_tombstone_beats_any_late_older_upsert
tests/sdk/test_sdk_projections.py::test_a_tombstone_beats_a_late_older_upsert
sandloom/core/tests/test_projection_kernel.py::test_a_purge_below_the_replay_horizon_is_refused
sandloom/core/tests/test_projection_kernel.py::test_the_watermark_is_raised_before_the_delete_and_never_lowered
sandloom/core/tests/test_projection_kernel.py::test_a_purge_refuses_during_a_rebuild_and_a_rebuild_keeps_the_mark
sandloom/core/tests/test_projection_kernel.py::test_an_event_older_than_the_watermark_is_dead_lettered
sandloom/core/tests/test_projection_watermarks_db.py::test_the_watermark_only_ever_rises
sandloom/core/tests/test_projection_watermarks_db.py::test_the_rebuild_flag_is_set_and_cleared_without_touching_the_mark
sandloom/core/tests/test_projection_kernel.py::test_concurrent_rebuilds_keep_the_purge_excluded_in_memory
sandloom/core/tests/test_projection_watermarks_db.py::test_concurrent_rebuilds_keep_the_purge_excluded_in_the_table
sandloom/core/tests/test_projection_kernel.py::test_a_rebuild_never_enters_a_running_purge_in_memory
sandloom/core/tests/test_projection_watermarks_db.py::test_a_rebuild_never_enters_a_running_purge_in_the_table
sandloom/core/tests/test_projection_watermarks_db.py::test_the_purge_window_holds_through_the_operations_units
sandloom/core/tests/test_projection_watermarks_db.py::test_racing_rebuild_marks_are_counted_exactly
tests/sdk/test_sdk_projection_ops.py::test_the_relational_purge_deletes_tombstones_older_than_the_cutoff
tests/sdk/test_sdk_projection_ops.py::test_a_store_purge_deletes_old_tombstones_only
tests/sdk/test_sdk_projection_ops.py::test_an_event_older_than_the_watermark_is_refused_on_apply
tests/sdk/test_sdk_projection_ops.py::test_only_a_projection_with_tombstones_gets_the_guard
tests/sdk/test_sdk_projection_purge_race.py::test_a_purge_racing_an_apply_never_resurrects_a_relational_row
tests/sdk/test_sdk_projection_purge_race.py::test_a_purge_racing_an_apply_never_resurrects_a_store_row
tests/sdk/test_sdk_projection_ops.py::test_an_independent_group_never_resurrects_a_store_tombstone
sandloom/core/tests/test_projection_kernel.py::test_the_conformance_kit_catches_a_store_whose_upserts_clear_tombstones
G-MSTORE-10 A store projection is tenant-native: each conditional write names its row's tenant, a row whose tenant is missing or differs from the bound tenant is refused, and store reads return only the bound tenant's rows: a read key or filter naming another tenant never overrides the bound one (it reads nothing, fail closed) and a store read without a bound tenant is refused. tests/sdk/test_sdk_projection_ops.py::test_a_store_row_of_another_tenant_is_refused_and_reads_are_per_tenant

G-OUTBOX

Id Statement Enforced by
G-OUTBOX-01 Inside a UoW, publish_event writes the event to outbox_messages in the same transaction; a rollback discards the row and nothing is published. sandloom/core/tests/test_uow_outbox.py::test_publish_event_enqueues_to_outbox_in_same_transaction
sandloom/core/tests/test_uow_outbox.py::test_rollback_discards_outbox_rows_and_nothing_is_published
sandloom/core/tests/test_pipelined_dispatch.py::test_rollback_publishes_nothing
G-OUTBOX-02 Outside a UoW scope publish_event (and publish_event_versions) raises UnitOfWorkRequiredError; only required=False publishes straight to the bus (at-most-once), and required=False inside a UoW still uses the outbox. sandloom/core/tests/test_uow_outbox.py::test_publish_event_outside_uow_raises
sandloom/core/tests/test_uow_outbox.py::test_publish_event_best_effort_outside_uow_goes_straight_to_bus
sandloom/core/tests/test_uow_outbox.py::test_best_effort_inside_uow_still_uses_the_outbox
sandloom/core/tests/test_uow_outbox.py::test_publish_event_versions_outside_uow_raises_unless_best_effort
G-OUTBOX-03 In module code every best-effort downgrade (publish_event / publish_event_versions / record_audit with required=False) must carry a '# sandloom: best-effort ' marker or sandloom check fails. tests/architecture/test_best_effort_effects_rule.py::test_fixture_violations_are_flagged_and_marked_calls_pass
tests/architecture/test_best_effort_effects_rule.py::test_aliased_effects_and_kwargs_splat_are_flagged
tests/architecture/test_best_effort_effects_rule.py::test_rule_runs_in_sandloom_check
rule best-effort-effects
G-OUTBOX-04 publish_event preflights channel name, payload schema and payload size before the event is written, so an event the bus would reject is refused to the caller instead of being committed to the outbox. sandloom/core/tests/test_infra_hardening.py::test_preflight_rejects_oversized_payload
sandloom/core/tests/test_infra_hardening.py::test_preflight_rejects_missing_schema
sandloom/core/tests/test_strict_mode.py::test_publish_missing_schema_raises_in_every_profile
sandloom/core/tests/test_event_bus_schemas.py::test_validation_failure_lists_every_violation
sandloom/core/tests/test_uow_outbox.py::test_publish_event_best_effort_outside_uow_is_preflighted
sandloom/core/tests/test_guardrails.py::test_event_payload_size_guard
gap (S): No test calls publish_event INSIDE a UoW with an invalid channel / schema-violating / oversized payload and asserts the call raises and no outbox_messages row exists; the in-UoW path (unit_of_work/effects.py preflight) is only covered indirectly.
G-OUTBOX-05 The dispatched envelope id equals the outbox row id and stays the same across post-commit dispatch and relay redeliveries. sandloom/core/tests/test_uow_outbox.py::test_committed_events_marked_delivered_after_dispatch
sandloom/core/tests/test_pipelined_dispatch.py::test_failed_batch_marks_nothing_and_relay_redelivers_same_ids
G-OUTBOX-06 The channel suffix is authoritative for the schema version (.v2 -> schema_version "v2"), so the outbox row and the dispatched envelope never disagree. sandloom/core/tests/test_review_fixes_v2.py::test_normalize_schema_version_derives_from_channel_suffix
G-OUTBOX-07 A failed post-commit dispatch never fails the committed operation: the row stays pending and the relay delivers it later (rows are marked delivered iff delivered; a crash between publish and mark leads to redelivery, never loss). sandloom/core/tests/test_uow_outbox.py::test_dispatch_failure_leaves_message_pending_for_relay
sandloom/core/tests/test_open_uow.py::test_post_commit_dispatch_failure_never_fails_committed_uow
sandloom/core/tests/test_post_commit_dispatcher.py::test_rows_are_marked_iff_delivered
sandloom/core/tests/test_post_commit_dispatcher.py::test_crash_between_publish_and_mark_is_redelivered_by_the_relay
G-OUTBOX-08 On the request / consumer / contract paths (wait=False) post-commit dispatch is a hand-off to a background coalescer that does not wait for the broker; db_session()/Celery/scripts (wait=True) publish in line; queue overflow falls back to the relay. sandloom/core/tests/test_post_commit_dispatcher.py::test_request_path_does_not_wait_for_the_broker
sandloom/core/tests/test_post_commit_dispatcher.py::test_db_session_path_publishes_and_marks_in_line
sandloom/core/tests/test_post_commit_dispatcher.py::test_queue_overflow_drops_to_the_relay
sandloom/core/tests/test_post_commit_dispatcher.py::test_consumer_session_hands_its_events_to_the_coalescer
G-OUTBOX-09 One UoW's own post-commit dispatch is sequential in emission order. sandloom/core/tests/test_uow_outbox.py::test_post_commit_dispatch_preserves_unit_of_work_order
sandloom/core/tests/test_pipelined_dispatch.py::test_queued_uows_go_out_in_one_ordered_batch
G-OUTBOX-10 The relay retries a failed row with capped per-row exponential backoff, and a row exhausting OUTBOX_RELAY_MAX_ATTEMPTS is dead-lettered (dead_at set, outbox_dead_total) instead of retried forever; a dead row can be requeued to pending. sandloom/core/tests/test_outbox_relay_backoff.py::test_failed_publish_defers_retry_with_backoff
sandloom/core/tests/test_outbox_relay_backoff.py::test_relay_backoff_grows_and_is_capped
sandloom/core/tests/test_outbox_relay_batch.py::test_failures_back_off_per_row_and_dead_letter
sandloom/core/tests/test_outbox_relay_backoff.py::test_requeue_dead_returns_rows_to_pending
G-OUTBOX-11 A broker outage burns no relay attempts: it trips a cool-down circuit (OUTBOX_RELAY_CIRCUIT_*) that skips relay ticks, so an outage never dead-letters healthy rows. sandloom/core/tests/test_outbox_relay_backoff.py::test_broker_outage_burns_no_attempts_and_stops_batch
sandloom/core/tests/test_outbox_relay_backoff.py::test_open_circuit_skips_relay_ticks
sandloom/core/tests/test_relay_batch_publish.py::test_relay_batch_outage_counts_no_attempt_and_trips_circuit
sandloom/core/tests/test_batch_publish.py::test_redis_availability_replies_do_not_burn_relay_attempts
G-OUTBOX-12 A poison row in a relay batch is isolated (per-row fallback) and still dead-lettered, while the other rows are delivered. sandloom/core/tests/test_relay_batch_publish.py::test_relay_batch_poison_falls_back_per_row
sandloom/core/tests/test_relay_batch_publish.py::test_relay_poison_row_still_dead_letters
sandloom/core/tests/test_kernel_hardening.py::test_outbox_relay_dead_letters_poison_message
sandloom/core/tests/test_kernel_hardening.py::test_relay_processes_each_message_independently
G-OUTBOX-13 Concurrent relays and post-commit marks settle every row exactly once: relays partition the backlog by lease, a settle never touches a reclaimed lease, and a late mark never resurrects a dead-lettered row. sandloom/core/tests/test_outbox_relay_batch.py::test_concurrent_relays_partition_the_backlog
sandloom/core/tests/test_outbox_relay_batch.py::test_settle_never_touches_a_reclaimed_lease
sandloom/core/tests/test_outbox_relay_batch.py::test_post_commit_mark_never_resurrects_a_dead_letter
sandloom/core/tests/test_outbox_relay_batch.py::test_concurrent_relays_and_marks_settle_every_row_once
G-OUTBOX-14 The relay retention purge deletes only delivered outbox rows older than OUTBOX_RETENTION_HOURS (never pending or dead rows), on a time cadence within a time budget. sandloom/core/tests/test_outbox_relay_batch.py::test_retention_purge_deletes_only_old_delivered_rows
sandloom/core/tests/test_outbox_relay_purge_cadence.py::test_purge_runs_on_a_time_cadence_not_a_tick_count
sandloom/core/tests/test_outbox_relay_purge_cadence.py::test_purge_stops_at_its_time_budget
G-OUTBOX-15 Events carry NO ordering guarantee, not even within one channel or aggregate; consumers order by a per-aggregate version applied with version guards, so redelivered or reordered events never roll state back. (The relay holding a channel's later rows behind an earlier row waiting to retry is an implementation detail, not a guarantee: one poison row delays its channel until it is dead-lettered.) sandloom/core/tests/test_projections.py::test_consumer_applies_and_is_monotonic_and_idempotent
sandloom/core/tests/test_projection_upsert_guards.py::test_null_stored_version_is_updatable
G-OUTBOX-16 OUTBOX_RELAY_ENABLED=off in a production app process is refused unless the accepted risk relay_disabled is declared, and under gunicorn exactly one elected worker runs the relay (others delegated). sandloom/core/tests/test_accepted_risks.py::test_relay_off_in_prod_is_refused_unless_accepted
sandloom/core/tests/test_accepted_risks.py::test_delegated_relay_needs_a_gunicorn_worker
sandloom/core/tests/test_gunicorn_conf.py::test_gunicorn_elects_exactly_one_relay_leader

G-EFFECT

Id Statement Enforced by
G-EFFECT-01 on_commit callbacks run only after the UoW commits, never on rollback, never twice; a failing callback is logged and never fails the committed request; outside a UoW the callback runs at once. sandloom/core/tests/test_on_commit.py::test_runs_after_commit_only
sandloom/core/tests/test_on_commit.py::test_rollback_never_runs
sandloom/core/tests/test_on_commit.py::test_failing_callback_does_not_fail_commit
sandloom/core/tests/test_on_commit.py::test_outside_uow_runs_immediately
G-EFFECT-02 Post-commit callbacks run under the tenant bound when they were registered, not the tenant bound at commit time. sandloom/core/tests/test_open_uow.py::test_post_commit_callbacks_see_the_tenant_bound_at_registration
G-EFFECT-03 before_commit checks run after the handler, in registration order, right before COMMIT; the first failing check rolls the UoW back and propagates; outside a UoW the check runs at once; the testing run_on_commit seam runs before_commit checks first. sandloom/core/tests/test_before_commit.py::test_checks_run_in_order_after_the_handler_and_before_commit
sandloom/core/tests/test_before_commit.py::test_a_failing_check_rolls_back_instead_of_committing
sandloom/core/tests/test_before_commit.py::test_outside_a_unit_of_work_the_check_runs_at_once
sandloom/core/tests/test_before_commit.py::test_testing_run_on_commit_runs_the_before_commit_checks_first
gap (S): No test proves that a before_commit check registered inside a rolled-back savepoint is skipped (ADR §3 stage 1).
G-EFFECT-04 All durable dedupe stores (consumer_inbox, contract_command_log, http_idempotency_log) are adapters of one relational claim implementation (sandloom.core.sql_alchemy.dedupe_log): a claim is decided by the in-flight transaction, a rolled-back claim leaves no record, the claim never flushes the caller's pending objects, and the bounded purge removes only due records. sandloom/core/tests/test_dedupe_record_adapters.py::test_every_store_is_an_adapter_of_one_dedupe_log
sandloom/core/tests/test_dedupe_record_adapters.py::test_rolled_back_claim_leaves_no_record
sandloom/core/tests/test_dedupe_record_adapters.py::test_concurrent_claim_is_decided_by_the_in_flight_transaction
sandloom/core/tests/test_dedupe_record_adapters.py::test_claim_never_flushes_the_callers_pending_objects
G-EFFECT-05 Every replay record shares one set of bounds from sandloom.core.effects / replay_limits: replayed body/result at most 256 KiB (larger -> non-replayable marker, never re-run) and a 24 h default TTL; argument digests are canonical. sandloom/core/tests/test_dedupe_record_adapters.py::test_tag_and_ttl_and_replay_bounds
sandloom/core/tests/test_dedupe_record_adapters.py::test_replay_outcome_caps_and_refuses_unencodable
sandloom/core/tests/test_dedupe_record_adapters.py::test_args_digest_is_canonical_with_a_repr_fallback
sandloom/core/tests/test_replay_limits.py::test_replay_body_caps_share_one_bound

G-INBOX

Id Statement Enforced by
G-INBOX-01 durable_inbox(consumer).claim(db, event_id) inserts with ON CONFLICT on UNIQUE(consumer, event_id) in the handler's own UoW: the first claim returns True, a duplicate returns False, and a handler's own constraint violation is never read as a duplicate. sandloom/core/tests/test_durable_inbox_claim.py::test_duplicate_claim_is_skipped
sandloom/core/tests/test_durable_inbox_claim.py::test_handler_constraint_violation_is_not_read_as_duplicate
sandloom/core/tests/test_kernel_hardening.py::test_durable_inbox_claims_once
sandloom/core/tests/test_dedupe_record_adapters.py::test_claimed_once_then_duplicate
G-INBOX-02 A durable=True consumer dedupes redeliveries by the DB claim on the envelope id and never touches the cache inbox. sandloom/core/tests/test_streams_event_bus.py::test_durable_consumer_dedupes_redelivery_without_cache_inbox
sandloom/core/tests/test_event_consumers.py::test_consumer_callback_advertises_durable_dedupe
G-INBOX-03 The cache-backed consumer inbox (durable=False) dedupes on the event id best-effort and fails open on a cache error (the effect stays at-least-once). sandloom/core/tests/test_transport.py::test_consumer_inbox_dedupes_on_event_id
sandloom/core/tests/test_transport.py::test_consumer_inbox_fails_open_on_cache_error
G-INBOX-04 Inbox purge removes old claims in bounded batches, each in its own UoW, backed by a declared purge index. sandloom/core/tests/test_durable_inbox_claim.py::test_purge_with_limit_deletes_old_claims_in_bounded_batches
sandloom/core/tests/test_durable_inbox_claim.py::test_inbox_purge_cli_loops_batches_in_separate_units_of_work
sandloom/core/tests/test_durable_inbox_claim.py::test_consumer_inbox_declares_the_purge_index

G-CMD

Id Statement Enforced by
G-CMD-01 A command contract's dedupe record is claimed in the provider's own UoW and commits iff the effect commits, so a retry after a crash following COMMIT replays the stored result and never re-executes. tests/integration/test_durable_command_idempotency.py::test_crash_after_commit_replays_and_never_reexecutes
tests/integration/test_durable_command_idempotency.py::test_billing_credit_spend_survives_crash_and_retry
G-CMD-02 Concurrent duplicates of a command execute exactly once; a duplicate that outwaits CONTRACT_DB_LOCK_TIMEOUT_MS gets 503 ContractCallInProgress. tests/integration/test_durable_command_idempotency.py::test_concurrent_duplicates_execute_exactly_once
tests/integration/test_durable_command_idempotency.py::test_duplicate_outwaiting_the_lock_timeout_is_in_progress
sandloom/core/tests/test_contract_parity.py::test_rpc_in_progress_maps_to_a_clear_unavailable
G-CMD-03 Reusing a command idempotency key with other arguments raises ContractIdempotencyConflict (409), locally and over RPC. tests/integration/test_durable_command_idempotency.py::test_same_key_other_arguments_conflicts_locally_and_over_rpc
sandloom/core/tests/test_service_rpc.py::test_command_contract_is_deduped_by_the_framework_locally_and_over_rpc
G-CMD-04 A failed command leaves no dedupe record (the retry runs afresh), and a provider killed mid-command holds no reservation (its uncommitted claim dies with the connection, no TTL stall). This includes an earlier attempt of the same call still in flight (attempt 1 unanswered, attempt 2 answered with a failure, attempt 1 then runs afresh and may succeed), the semantics of a client retry after a failure; the unanswered attempt keeps the caller's request in doubt (G-UNIT-02). tests/integration/test_durable_command_idempotency.py::test_failed_command_leaves_no_record_and_retry_executes
tests/integration/test_durable_command_idempotency.py::test_provider_killed_mid_command_does_not_block_the_retry
G-CMD-05 Local and RPC calls of a command share one dedupe namespace/record (a remote retry of a local call replays). tests/integration/test_durable_command_idempotency.py::test_local_and_rpc_share_one_record
sandloom/core/tests/test_contract_runtime.py::test_direct_command_call_shares_the_registered_dedupe_namespace
G-CMD-06 A command result larger than 256 KiB is stored as a non-replayable marker: a repeat raises ContractResultNotReplayable (409) and the method is never re-run. tests/integration/test_durable_command_idempotency.py::test_oversized_result_is_never_reexecuted
sandloom/core/tests/test_contract_parity.py::test_command_dedupe_is_shared_bounded_and_size_capped
G-CMD-07 A command record expires after CONTRACT_COMMAND_DEDUPE_TTL_SECONDS (the key then runs afresh by taking over the expired record), an in-flight claim past its TTL is never taken over, and purge never deletes a concurrently taken-over record. tests/integration/test_durable_command_idempotency.py::test_expired_record_is_taken_over_and_purged
tests/integration/test_durable_command_idempotency.py::test_an_in_flight_claim_past_its_ttl_is_never_taken_over
tests/integration/test_durable_command_idempotency.py::test_purge_never_deletes_a_record_taken_over_concurrently
G-CMD-08 A command contract must name its mandatory idempotency_key argument at registration and commits its own UoW. sandloom/core/tests/test_contract_runtime.py::test_command_contract_must_name_its_idempotency_key
sandloom/core/tests/test_contract_runtime.py::test_command_commits_and_requires_idempotency_key
G-CMD-09 Without a database command log (no command_idempotency feature) command dedupe falls back to the cache, fail-closed: a cache outage raises ContractIdempotencyUnavailable (503) and the method does not run; the fallback follows IDEMPOTENCY_CACHE_INSTANCE. sandloom/core/tests/test_service_rpc.py::test_command_contract_fails_closed_when_the_store_is_down
sandloom/core/tests/test_idempotency_cache_instance.py::test_contract_command_cache_fallback_follows_the_setting

G-IDEM

Id Statement Enforced by
G-IDEM-01 Cache-form idempotency_guard replays a successful response (same status/body, Idempotent-Replay: true) without re-running the endpoint. sandloom/core/tests/test_api_robustness.py::test_idempotency_replays_successful_response
sandloom/core/tests/test_idempotency_record_format.py::test_json_body_is_stored_raw_and_replays_byte_identical
G-IDEM-02 A retry while the first request is pending gets 409; the pending reservation has a short TTL that is renewed while the request runs and self-heals if the request dies. sandloom/core/tests/test_api_robustness.py::test_idempotency_pending_request_conflicts
sandloom/core/tests/test_api_robustness.py::test_idempotency_pending_reservation_uses_short_ttl
sandloom/core/tests/test_idempotency_pending_renewal.py::test_a_running_request_keeps_its_reservation_past_the_pending_ttl
sandloom/core/tests/test_idempotency_pending_renewal.py::test_a_dead_requests_reservation_still_self_heals
G-IDEM-03 After a failed attempt (error response or unhandled exception) the key is released so a retry reserves afresh; the error response is sent before the release. sandloom/core/tests/test_api_robustness.py::test_idempotency_failed_attempt_allows_retry
sandloom/core/tests/test_api_robustness.py::test_idempotency_unhandled_exception_releases_key
sandloom/core/tests/test_security_review_regressions.py::test_error_response_is_sent_before_cache_release
G-IDEM-04 Reusing an Idempotency-Key with a different request body gets 422 (cache and durable forms); a missing key is rejected. sandloom/core/tests/test_api_robustness.py::test_idempotency_rejects_key_reused_with_different_body
sandloom/core/tests/test_durable_idempotency_guard.py::test_same_key_other_request_is_422
sandloom/core/tests/test_api_robustness.py::test_idempotency_guard_requires_key
G-IDEM-05 Every cache-guard transition is a compare-and-set on the reserving request's owner token: an expired owner can neither release nor finalise over a retry's reservation. sandloom/core/tests/test_idempotency_cas.py::test_redis_cas_primitives
sandloom/core/tests/test_idempotency_cas.py::test_expired_owner_cannot_release_a_retrys_reservation
sandloom/core/tests/test_idempotency_cas.py::test_expired_owner_cannot_finalise_over_a_retrys_reservation
G-IDEM-06 Idempotency keys are scoped per caller and tenant (never replayed across tenants), and an anonymous caller never gets a stored body replayed (status only). sandloom/core/tests/test_api_robustness.py::test_idempotency_never_replays_across_tenants
sandloom/core/tests/test_api_robustness.py::test_idempotency_anonymous_retry_is_deduped_without_body_replay
sandloom/core/tests/test_durable_idempotency_guard.py::test_key_is_scoped_to_caller_and_tenant
sandloom/core/tests/test_durable_idempotency_guard.py::test_anonymous_caller_gets_status_only
G-IDEM-07 A response larger than the capture cap is returned in full but stored as a non-replayable success marker (never re-executed, body not preserved); a corrupt record is discarded instead of blocking the key. sandloom/core/tests/test_api_robustness.py::test_idempotency_large_response_returns_full_body_stores_marker
sandloom/core/tests/test_idempotency_record_format.py::test_an_old_reader_replays_a_new_record_status_only_never_re_executes
sandloom/core/tests/test_api_robustness.py::test_idempotency_corrupt_record_does_not_block_key
G-IDEM-08 With fail_closed=True a cache outage answers 503 (never a possible duplicate); the default fail-open guard lets the request through. sandloom/core/tests/test_api_robustness.py::test_idempotency_fail_closed_returns_503_on_cache_outage
sandloom/core/tests/test_api_robustness.py::test_idempotency_fail_open_allows_on_cache_outage
sandloom/core/tests/test_idempotency_cache_instance.py::test_fail_closed_follows_the_idempotency_instance
G-IDEM-09 idempotency_guard(durable=True) claims its record in the request's DbSession UoW and commits it with the response: a retry replays the stored response and the effect runs once; a rolled-back request leaves no record; a returned 4xx is replayed; a returned 5xx is never replayed. sandloom/core/tests/test_durable_idempotency_guard.py::test_replay_returns_the_stored_response_and_runs_once
sandloom/core/tests/test_durable_idempotency_guard.py::test_rolled_back_request_leaves_no_record
sandloom/core/tests/test_durable_idempotency_guard.py::test_returned_4xx_is_the_committed_outcome_and_replays
sandloom/core/tests/test_durable_idempotency_guard.py::test_returned_5xx_is_never_replayed
G-IDEM-10 An in-flight duplicate of a durable-guarded request gets 409 at once (advisory try-lock), never a wait on the first request's uncommitted claim. sandloom/core/tests/test_durable_idempotency_guard.py::test_concurrent_in_flight_duplicate_is_409_at_once
G-IDEM-11 After an ambiguous COMMIT a durable-guarded retry replays if the transaction committed and runs afresh if it did not — never twice. sandloom/core/tests/test_durable_idempotency_guard.py::test_ambiguous_commit_that_committed_is_replayed_not_rerun
sandloom/core/tests/test_durable_idempotency_guard.py::test_ambiguous_commit_that_did_not_commit_runs_afresh
G-IDEM-12 Durable guard records expire after the guard TTL (taken over then purged); oversized/streamed responses replay status only; a route whose response was not captured is refused rather than re-run; routes added after create_app are captured too. sandloom/core/tests/test_durable_idempotency_guard.py::test_expired_record_is_taken_over_and_purged
sandloom/core/tests/test_durable_idempotency_guard.py::test_unreplayable_response_replays_status_only
sandloom/core/tests/test_durable_idempotency_guard.py::test_route_without_capture_is_refused_not_rerun
sandloom/core/tests/test_durable_idempotency_guard.py::test_route_added_after_create_app_is_captured
G-IDEM-14 create_app refuses (GuardOrderError) a route where FastAPI resolves the idempotency guard before its authenticating dependency. sandloom/core/tests/test_guard_order.py::test_guard_in_route_dependencies_before_param_auth_is_refused
sandloom/core/tests/test_guard_order.py::test_guard_param_before_auth_param_is_refused
sandloom/core/tests/test_guard_order.py::test_custom_authenticator_counts_once_marked
sandloom/core/tests/test_guard_order.py::test_bundled_app_routes_pass
G-IDEM-15 A valid token's revocation and block state is checked live before an idempotency reservation or replay, even when the guard precedes the endpoint's auth dependency. sandloom/core/tests/test_security_review_regressions.py::test_replay_checks_live_auth_before_cache_lookup
G-IDEM-16 Cache-guard records live in SECURITY_CACHE_INSTANCE unless IDEMPOTENCY_CACHE_INSTANCE moves them (with task-enqueue dedupe and the command cache fallback); revocation marks never move, and the strict profile refuses an idempotency instance that is not its own durable Redis. sandloom/core/tests/test_idempotency_cache_instance.py::test_default_keeps_guard_records_in_the_security_cache
sandloom/core/tests/test_idempotency_cache_instance.py::test_reserve_replay_conflict_release_use_the_idempotency_instance
sandloom/core/tests/test_idempotency_cache_instance.py::test_revocation_marks_stay_in_the_security_cache
sandloom/core/tests/test_idempotency_cache_instance.py::test_strict_profile_refuses_an_unsafe_idempotency_instance
G-IDEM-17 The automatic guard (user writes carrying Idempotency-Key, with no explicit guard, no_idempotency or operation intent) always replays the status only, for a DbSession write and for an ordinary write through db() (the lazy request unit holds the durable record) (mode S: Idempotent-Replay: true, empty body, the handler never runs again), including after an ownership transfer; replay_safe(reason=) opts a non-sensitive route in to body replay (mode B); a sensitive write is always S; the replay is decided after every route-level dependency (session check, gates, step-up); a different request under the same key is 422, and cross-route or cross-tenant reuse never replays. tests/integration/test_automatic_idempotency.py::test_the_automatic_guard_replays_the_status_only_never_a_body
tests/integration/test_automatic_idempotency.py::test_replay_safe_replays_the_body_after_every_route_dependency
tests/integration/test_automatic_idempotency.py::test_a_revoked_session_is_refused_before_any_replay
tests/integration/test_automatic_idempotency.py::test_mode_x_re_runs_authorization_and_the_handler
tests/integration/test_automatic_idempotency.py::test_a_lazy_db_write_gets_the_durable_status_replay
G-IDEM-18 A write route without a request unit, or whose request unit's database keeps no durable request log, gets the fail-closed in-flight form of the automatic guard: a concurrent duplicate is 409, a cache outage 503, and nothing is stored. tests/integration/test_automatic_idempotency.py::test_without_a_request_unit_the_guard_is_the_fail_closed_in_flight_form

G-TASK

Id Statement Enforced by
G-TASK-01 enqueue(..., idempotency_key=) publishes one real message for successful retries (task id replayed within dedupe_ttl_seconds), including parallel retries; the replay window expires after the TTL. sandloom/core/tests/test_enqueue_deduplication.py::test_successful_retries_publish_one_real_message
sandloom/core/tests/test_enqueue_deduplication.py::test_parallel_retries_never_publish_twice
sandloom/core/tests/test_enqueue_deduplication.py::test_replay_window_expires_on_real_redis
sandloom/core/tests/test_enqueue_deduplication.py::test_lost_finalize_ack_keeps_the_original_id_on_retry
G-TASK-02 Reusing an enqueue idempotency key with other arguments fails with 409; keys are tenant-scoped and the payload is snapshotted before reserving. sandloom/core/tests/test_enqueue_deduplication.py::test_key_cannot_be_reused_for_other_arguments
sandloom/core/tests/test_enqueue_deduplication.py::test_tenants_do_not_share_enqueue_keys
sandloom/core/tests/test_enqueue_deduplication.py::test_payload_is_snapshotted_before_reserving
G-TASK-03 Enqueue dedupe fails closed: an unavailable dedupe store prevents the publish (503), and an ambiguous broker send keeps the key pending (503 task_enqueue_uncertain, never republished) until reconciled; deterministic send/serialisation errors release the reservation. sandloom/core/tests/test_enqueue_deduplication.py::test_unavailable_store_prevents_publish
sandloom/core/tests/test_enqueue_deduplication.py::test_uncertain_broker_ack_is_not_republished
sandloom/core/tests/test_enqueue_deduplication.py::test_ambiguous_send_error_still_keeps_the_key_pending
sandloom/core/tests/test_enqueue_deduplication.py::test_deterministic_send_error_releases_the_reservation
G-TASK-04 run_async runs task coroutines on one persistent event loop per worker process (fresh after fork, engines disposed on it at shutdown) and refuses to run inside a running loop. sandloom/core/tests/test_async_runner.py::test_sequential_calls_share_one_persistent_loop
sandloom/core/tests/test_async_runner.py::test_inherited_loop_is_replaced_after_fork
sandloom/core/tests/test_async_runner.py::test_run_async_inside_a_running_loop_is_rejected
sandloom/core/tests/test_async_runner.py::test_shutdown_runs_cleanups_on_the_worker_loop_then_closes_it
G-TASK-05 Celery tasks store no result by default (task_ignore_result, also on driver-built apps, enqueue subscribes no result consumer), the worker prefetches one message, and acks_late stays a per-task opt-in. sandloom/core/tests/test_cache_batch_and_celery_defaults.py::test_worker_reliability_defaults
sandloom/core/tests/test_cache_batch_and_celery_defaults.py::test_driver_built_apps_ignore_results_by_default
sandloom/core/tests/test_cache_batch_and_celery_defaults.py::test_enqueue_never_subscribes_a_result_consumer

G-WINDOW

Id Statement Enforced by
G-WINDOW-01 guarantee_window_problems reports command_dedupe_below_retry_horizon when CONTRACT_COMMAND_DEDUPE_TTL_SECONDS is below the relay redelivery horizon (OUTBOX_RELAY_MAX_ATTEMPTS x capped backoff); defaults satisfy every window. sandloom/core/tests/test_guarantee_windows.py::test_defaults_hold_every_window
sandloom/core/tests/test_guarantee_windows.py::test_relay_horizon_is_the_capped_backoff_sum
sandloom/core/tests/test_guarantee_windows.py::test_command_ttl_below_the_relay_horizon
G-WINDOW-02 Production refuses to start on a violated guarantee window (GuaranteeWindowError naming the risk id), other environments warn, and a window is waived only by an accepted risk registered in accepted_risks. sandloom/core/tests/test_guarantee_windows.py::test_production_refuses_and_names_the_risk
sandloom/core/tests/test_guarantee_windows.py::test_dev_warns
sandloom/core/tests/test_guarantee_windows.py::test_accepted_risks_are_not_refused
sandloom/core/tests/test_guarantee_windows.py::test_create_app_refuses_in_production
G-WINDOW-03 sandloom outbox purge --inbox-days D refuses D < EVENT_STREAM_RETENTION_SECONDS + 1 day (any D when retention is unbounded) unless --force (risk inbox_purge_below_redelivery). sandloom/core/tests/test_guarantee_windows.py::test_inbox_purge_window
sandloom/core/tests/test_durable_inbox_claim.py::test_inbox_purge_inside_the_redelivery_window_is_refused
sandloom/core/tests/test_guarantee_windows.py::test_window_risk_ids_are_registered_accepted_risks

G-EVENT

Id Statement Enforced by
G-EVENT-01 A consumer registered by a module gets the durable group :: equally named consumers of two modules (or of one module in two services) get distinct groups and each receives every event, while replicas of one service share the group. sandloom/core/tests/test_event_consumers.py::test_same_consumer_name_in_two_modules_gets_two_groups
sandloom/core/tests/test_streams_split_identity.py::test_same_name_in_two_services_each_receive_every_event
sandloom/core/tests/test_streams_split_identity.py::test_replicas_of_one_service_share_the_group
G-EVENT-02 Startup fails when two consumers resolve to one group in a process, when an explicit group= is reused by another module, or when the group is already owned by another module / database (sandloom:group-owner fingerprint); consumer names are unique across event and projection consumers. sandloom/core/tests/test_streams_split_identity.py::test_duplicate_group_in_one_process_fails_startup
sandloom/core/tests/test_streams_split_identity.py::test_explicit_group_reused_by_other_module_fails_startup
sandloom/core/tests/test_streams_split_identity.py::test_group_owned_by_other_database_fails_startup
sandloom/core/tests/test_event_consumers.py::test_consumer_name_unique_across_event_and_projection
G-EVENT-03 Required consumers (the default) abort startup when they cannot connect (all consumers are still attempted, transient failures retried within CONSUMER_STARTUP_TIMEOUT_SECONDS), and /ready fails while a required live subscription is lost; required=False consumers never delay startup or readiness. sandloom/core/tests/test_consumer_runtime.py::test_lifespan_aborts_when_required_consumer_cannot_start
sandloom/core/tests/test_consumer_runtime.py::test_ready_endpoint_rejects_unavailable_required_consumer
sandloom/core/tests/test_consumer_runtime.py::test_required_runtime_readiness_tracks_live_subscription_state
sandloom/core/tests/test_consumer_runtime.py::test_optional_consumer_never_delays_application_startup
G-EVENT-04 A consumer subscription whose task dies is restarted by the supervised runtime, and a permanent subscription contract error is not retried forever. sandloom/core/tests/test_consumer_runtime.py::test_runtime_restarts_a_subscription_whose_task_dies
sandloom/core/tests/test_consumer_runtime.py::test_permanent_subscription_contract_error_is_not_retried_forever
sandloom/core/tests/test_consumer_runtime.py::test_required_consumer_retries_transient_startup_failure
G-EVENT-05 Each consumer delivery runs async (session, envelope) in a UoW: commit on success; a handler error leaves the delivery for redelivery (rollback). sandloom/core/tests/test_event_consumers.py::test_start_event_consumers_wires_and_delivers
sandloom/core/tests/test_streams_event_bus.py::test_failed_handler_leaves_message_for_redelivery
sandloom/core/tests/test_inmemory_bus_delivery.py::test_failing_consumer_is_retried_until_success
gap (S): No test asserts the handler's DB writes are rolled back when the handler raises (the consumer UoW rollback itself); tests check redelivery with fake sessions only.
G-EVENT-06 A consumer with dead_letter=False retries a failing delivery forever with capped backoff instead of dead-lettering it, while default consumers are dead-lettered after the bus's delivery budget. sandloom/core/tests/test_revocation_never_dead_lettered.py::test_in_memory_bus_retries_a_never_dead_letter_consumer_forever
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_streams_outage_longer_than_the_old_budget_is_applied_not_dlqd
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_streams_default_consumers_are_still_dead_lettered
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_redis_streams_never_dead_letter_group_survives_the_old_budget
G-EVENT-07 A handler raising PoisonEventError (public in sandloom.core.di) is dead-lettered at once, even on a dead_letter=False consumer. sandloom/core/tests/test_revocation_never_dead_lettered.py::test_in_memory_bus_dead_letters_poison_immediately
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_streams_malformed_revocation_is_dead_lettered_at_once
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_poison_error_is_public
G-EVENT-08 bind_tenant_from="" binds the payload's tenant for the handler; consumers otherwise never inherit the publisher's tenant context. sandloom/core/tests/test_event_consumers.py::test_bind_tenant_from_binds_the_envelope_tenant
sandloom/core/tests/test_inmemory_bus_delivery.py::test_consumer_does_not_inherit_publisher_tenant
G-EVENT-09 start_from (earliest | latest | timestamp) picks where a NEW group starts and is validated together with group=. sandloom/core/tests/test_event_consumers.py::test_start_from_and_group_are_validated
sandloom/core/tests/test_streams_split_identity.py::test_start_from_latest_skips_history
sandloom/core/tests/test_streams_split_identity.py::test_start_from_timestamp
G-EVENT-10 The Streams transport redelivers unacked entries, reclaims dead consumers' pending entries via XAUTOCLAIM, dead-letters an entry to .dlq after exactly the max deliveries, and acks the source only after the DLQ append succeeded. sandloom/core/tests/test_transport.py::test_unacked_message_is_redelivered
sandloom/core/tests/test_transport.py::test_reclaim_advances_server_scan_cursor
sandloom/core/tests/test_transport.py::test_poison_dead_lettered_after_exactly_max_deliveries
sandloom/core/tests/test_transport.py::test_dlq_append_failure_leaves_source_message_unacked
G-EVENT-11 A DLQ replay is targeted at the group that dead-lettered the entry; other groups ack-skip it (Streams and memory transports). sandloom/core/tests/test_streams_split_identity.py::test_dlq_replay_reaches_only_the_failed_group
sandloom/core/tests/test_streams_split_identity.py::test_memory_dlq_replay_is_targeted_too
sandloom/core/tests/test_admin_operations.py::test_stream_dlq_replay_targets_the_group_and_audits_first
G-EVENT-12 Streams are trimmed by age (EVENT_STREAM_RETENTION_SECONDS) and optionally by length (EVENT_STREAM_MAXLEN) on publish. sandloom/core/tests/test_streams_split_identity.py::test_publish_trims_entries_older_than_retention
sandloom/core/tests/test_streams_split_identity.py::test_publish_caps_stream_length
G-EVENT-13 Re-registering an identical event schema is a no-op, while a breaking change (new required field or removed required field on the same version) fails registration / module load. sandloom/core/tests/test_core_event_schemas.py::test_re_register_idempotent
sandloom/core/tests/test_schema_compat.py::test_add_required_field_same_version_rejected
sandloom/core/tests/test_schema_compat.py::test_remove_required_field_rejected
sandloom/core/tests/test_schema_compat.py::test_add_optional_field_allowed
G-EVENT-14 Durable/required consumers are refused on the volatile Pub/Sub bus unless the volatile_event_bus risk is accepted, and the memory/redis Pub/Sub drivers are refused in production without it. sandloom/core/tests/test_volatile_bus_guards.py::test_durable_consumer_refused_on_pubsub_bus
sandloom/core/tests/test_volatile_bus_guards.py::test_memory_bus_allowed_in_dev_refused_in_prod
sandloom/core/tests/test_volatile_bus_guards.py::test_driver_refuses_volatile_bus_in_prod

G-AUDIT

Id Statement Enforced by
G-AUDIT-01 record_audit writes the audit_log row in the current UoW, so it commits iff the business change commits. sandloom/core/tests/test_audit_and_secrets.py::test_record_audit_writes_in_uow
sandloom/core/tests/test_revocation_freshness.py::test_block_write_outage_is_503_and_rolls_back_audit
sandloom/core/tests/test_fused_effect_rows.py::test_outbox_and_audit_rows_are_one_statement
gap (S): No generic test that a plain record_audit followed by a UoW rollback leaves no audit_log row; only the block_user path proves it.
G-AUDIT-02 record_audit defaults to required=True: outside a UoW, with a non-UUID actor_id, or without an audit store it raises AuditUnavailableError; required=False is skipped silently (best-effort, marker required in module code). sandloom/core/tests/test_audit_and_secrets.py::test_record_audit_outside_uow_raises_by_default
sandloom/core/tests/test_audit_and_secrets.py::test_record_audit_best_effort_outside_uow_is_skipped
sandloom/core/tests/test_audit_and_secrets.py::test_record_audit_default_rejects_invalid_actor
sandloom/core/tests/test_testing_harness.py::test_required_audit_without_uow_raises
rule best-effort-effects
gap (S): The 'no audit store' branch of AuditUnavailableError (database driver without the audit feature) has no direct raising test (test_audit_store_skips_unsupported_backend only checks the store is absent).

G-AUTH

Id Statement Enforced by
G-AUTH-01 Every framework guard and the single bearer parser (bearer_claims) reject a JWT whose token_type is not "access" (e.g. a refresh token or a token without token_type), so it can never act as an access token. tests/integration/test_review_hardening.py::test_guard_rejects_token_without_access_type
sandloom/core/tests/test_auth_rotation_revocation.py::test_bearer_claims_is_the_single_access_token_parser
sandloom/core/tests/test_auth_rotation_revocation.py::test_idempotency_and_tenant_scope_ignore_non_access_bearers
G-AUTH-02 An access token without iat or exp is rejected (401 at the guard). sandloom/core/tests/test_auth_rotation_revocation.py::test_decode_requires_iat
tests/integration/test_shared_authz_review.py::test_token_without_iat_is_rejected
tests/integration/test_review_hardening.py::test_decode_token_requires_exp
G-AUTH-03 JWT_ALGORITHM accepts only HS256/HS384/HS512, EdDSA, RS256 or ES256 (none and anything else fail settings validation); each asymmetric algorithm is bound to one key type at boot (wrong key type, RSA < 2048 bit, wrong EC curve refuse startup) and the HS and asymmetric key families are mutually exclusive. sandloom/core/tests/test_asymmetric_jwt.py::test_alg_none_is_rejected
sandloom/core/tests/test_asymmetric_jwt.py::test_key_type_is_bound_to_the_algorithm
sandloom/core/tests/test_asymmetric_jwt.py::test_short_rsa_keys_are_refused
sandloom/core/tests/test_asymmetric_jwt.py::test_hmac_material_is_refused_with_asymmetric_and_vice_versa
G-AUTH-04 In asymmetric mode verification accepts exactly JWT_ALGORITHM with the one public key named by the token's kid: a token without kid, with an unknown kid, of another algorithm, HMAC-signed with the public PEM or with JWT_SECRET is rejected. sandloom/core/tests/test_asymmetric_jwt.py::test_token_without_or_with_unknown_kid_is_rejected
sandloom/core/tests/test_asymmetric_jwt.py::test_hs256_token_signed_with_the_public_key_is_rejected
sandloom/core/tests/test_asymmetric_jwt.py::test_hs256_token_signed_with_jwt_secret_is_rejected_in_asymmetric_mode
sandloom/core/tests/test_asymmetric_jwt.py::test_token_of_another_asymmetric_algorithm_is_rejected
G-AUTH-05 Startup role check (critical, the process refuses to serve): an issuer (a process loading a module with issues_tokens=True) without JWT_PRIVATE_KEY, or a verify-only process holding JWT_PRIVATE_KEY or JWT_SECRET, is refused; in prod a split process on a shared HS secret is refused unless the accepted risk shared_hs_secret_split is set, and never under multi_tenant_strict. sandloom/core/tests/test_asymmetric_jwt.py::test_startup_role_check
sandloom/core/tests/test_asymmetric_jwt.py::test_verify_only_process_may_not_hold_a_symmetric_secret
sandloom/core/tests/test_asymmetric_jwt.py::test_prod_split_deployment_refuses_a_shared_hs_secret
sandloom/core/tests/test_asymmetric_jwt.py::test_create_app_refuses_key_material_that_fails_the_boot_check
sandloom/core/tests/test_production_profile.py::test_startup_role_check_has_no_shared_secret_opt_out_under_the_profile
G-AUTH-06 A verify-only process (asymmetric mode, no private key) cannot mint access tokens; in the microservices split every identity-minted access token is EdDSA under the issuer's kid and verifies in processes holding only public keys. sandloom/core/tests/test_asymmetric_jwt.py::test_verify_only_process_cannot_mint
tests/split_conformance/test_split_conformance.py::test_identity_issues_asymmetric_tokens
G-AUTH-07 Symmetric key rotation: secrets in JWT_PREVIOUS_SECRETS still verify but never mint, a token whose kid equals JWT_KID verifies with JWT_SECRET only, and in prod previous secrets obey the JWT_SECRET strength rules and may not contain the current secret. sandloom/core/tests/test_auth_rotation_revocation.py::test_kid_is_stamped_and_previous_secret_still_verifies
sandloom/core/tests/test_auth_rotation_revocation.py::test_current_kid_is_verified_with_current_secret_only
sandloom/core/tests/test_auth_rotation_revocation.py::test_prod_previous_secrets_follow_jwt_secret_rules
sandloom/core/tests/test_auth_rotation_revocation.py::test_previous_secrets_must_not_contain_current
G-AUTH-08 Asymmetric key rotation without forced logout: every kid in JWT_PUBLIC_KEYS verifies, a retired (removed) kid is rejected at once, and startup refuses a JWT_PRIVATE_KEY that is not the private half of JWT_PUBLIC_KEYS[JWT_KID]. sandloom/core/tests/test_asymmetric_jwt.py::test_rotation_both_kids_verify_and_retired_kid_is_rejected
sandloom/core/tests/test_asymmetric_jwt.py::test_private_key_must_match_its_published_kid
sandloom/core/tests/test_asymmetric_jwt.py::test_kid_bound_to_a_different_key_is_rejected
G-AUTH-09 When JWT_ISSUER / JWT_AUDIENCE are set, tokens carry them and verification rejects a token of another issuer or audience. sandloom/core/tests/test_review_fixes_v2.py::test_jwt_issuer_audience_enforced_when_configured
G-AUTH-10 Prod refuses to start with a weak or placeholder JWT_SECRET (secrets and tokens must be >= 32 chars and not placeholders). sandloom/core/tests/test_ops_security_hardening.py::test_prod_rejects_weak_or_placeholder_jwt_secret
sandloom/core/tests/test_ops_security_hardening.py::test_prod_accepts_random_jwt_secret
G-AUTH-11 A claims contributor (core.add_claims_contributor) runs in its module's own UoW and is auto-exposed as .claims.v1; a failing critical contributor, local or remote, aborts minting with ClaimsUnavailable (503), the first critical failure in registration order winning. sandloom/core/tests/test_contract_runtime.py::test_local_contributor_runs_in_its_own_uow_and_is_exposed
sandloom/core/tests/test_contract_runtime.py::test_critical_local_contributor_failure_is_503
sandloom/core/tests/test_contract_runtime.py::test_critical_remote_contributor_failure_is_503
sandloom/core/tests/test_contract_runtime.py::test_first_critical_failure_in_registration_order_wins
G-AUTH-12 Every minted token carries claims_sources / claims_sources_failed; a module's claim is trusted only when that module is in claims_sources and not in claims_sources_failed, otherwise the fact is unknown and escalates to a live check. sandloom/core/tests/test_ops_security_hardening.py::test_claims_provenance_always_stamped
tests/integration/test_claims_provenance_and_subscribed.py::test_authoritative_provenance_trusts_only_listed_owner
tests/integration/test_claims_provenance_and_subscribed.py::test_owner_in_both_lists_is_not_trusted
tests/integration/test_claims_provenance_and_subscribed.py::test_subscribed_escalates_to_live_when_billing_failed_at_mint
G-AUTH-13 Claims contributors cannot override the identity claims (sub, email, email_verified, token_type). sandloom_identity/tests/test_token_claims_and_revocation.py::test_unverified_user_and_contributors_cannot_override_identity
G-AUTH-14 Identity stamps auth_time (epoch seconds of the primary sign-in) and carries it unchanged through refresh; recency (step-up) checks read auth_time, never iat, so a refreshed token with a fresh iat is not 'recent'. sandloom_identity/tests/services/test_reauth_auth_time.py::test_fresh_iat_with_old_auth_time_is_not_recent
sandloom_identity/tests/services/test_reauth_auth_time.py::test_refresh_carries_auth_time_unchanged
G-AUTH-15 Password hashing is argon2id from PASSWORD_HASH_PROFILE (strong default m=65536/t=3/p=4, owasp_min m=19456/t=2/p=1, custom requiring every value); explicit PASSWORD_HASH_* values override the preset and the configured hasher follows settings. sandloom/core/tests/test_password_hash_config.py::test_defaults_are_unchanged
sandloom/core/tests/test_password_hash_config.py::test_owasp_min_preset
sandloom/core/tests/test_password_hash_config.py::test_explicit_values_override_the_preset
sandloom/core/tests/test_password_hash_config.py::test_custom_requires_every_value
G-AUTH-16 Production refuses password-hash parameters below the OWASP argon2id minimum unless the accepted risk weak_password_hash is set (outside prod only a warning). sandloom/core/tests/test_password_hash_config.py::test_prod_refuses_weak_parameters_with_a_fix
sandloom/core/tests/test_password_hash_config.py::test_prod_accepts_presets_and_the_explicit_opt_out
sandloom/core/tests/test_password_hash_config.py::test_every_owasp_equivalent_meets_the_floor
G-AUTH-17 A successful login re-hashes a hash with other parameters (upgrade or downgrade) in the same UoW; a failed login never re-hashes and a re-hash failure never blocks the login (counted). sandloom_identity/tests/services/test_password_rehash_on_login.py::test_login_upgrades_a_weaker_hash
sandloom_identity/tests/services/test_password_rehash_on_login.py::test_failed_login_never_rehashes
sandloom_identity/tests/services/test_password_rehash_on_login.py::test_rehash_failure_does_not_block_login
sandloom/core/tests/test_password_hash_config.py::test_rehash_failure_never_raises_and_is_counted
G-AUTH-18 A failed login does the same work (one argon2 pool submission against a startup-warmed dummy hash, same cache/SQL steps) whether or not the e-mail has an account, so neither response nor timing reveals account existence. sandloom_identity/tests/services/test_login_timing_equalizer.py::test_wrong_password_and_unknown_email_do_identical_work
sandloom_identity/tests/services/test_login_timing_equalizer.py::test_unknown_email_submits_to_the_password_pool_once
sandloom_identity/tests/services/test_login_timing_equalizer.py::test_identity_warms_the_dummy_hash_at_startup
G-AUTH-19 TOTP secrets are encrypted at rest with TWOFA_ENCRYPTION_KEY (comma list, newest first, for rotation); in prod with identity loaded the key is mandatory and must be strong (critical startup refusal), only outside prod an unset key derives from JWT_SECRET. sandloom_identity/tests/test_token_claims_and_revocation.py::test_twofa_key_is_mandatory_in_prod
sandloom_identity/tests/test_module_registration.py::test_missing_twofa_key_registers_a_critical_refusal
sandloom/core/tests/test_auth_rotation_revocation.py::test_prod_twofa_key_must_be_strong
sandloom_identity/tests/services/test_twofa_service.py::TestSecretCrypto::test_key_rotation

G-REVOKE

Id Statement Enforced by
G-REVOKE-01 An access token is revoked iff its issue instant iat_us (microseconds) is <= its subject's revocation mark; a token without iat_us is revoked by any mark; every framework guard rejects a revoked token with 401 'Token has been revoked'. sandloom/core/tests/test_revocation_windows.py::test_token_issued_earlier_in_the_same_second_is_revoked
sandloom/core/tests/test_revocation_windows.py::test_a_token_without_iat_us_is_revoked_by_any_mark
sandloom/core/tests/test_auth_rotation_revocation.py::test_revoked_token_is_rejected_by_every_framework_guard
G-REVOKE-02 The mark is written at now + AUTH_REVOCATION_CLOCK_SKEW_SECONDS, so a token from an issuer whose clock runs ahead is still revoked, while an issuer that ran the issuance check (ensure_user_not_blocked) and tokens minted later in the revoking request are stamped past it. sandloom/core/tests/test_revocation_windows.py::test_skew_margin_rejects_tokens_from_an_issuer_clock_running_ahead
sandloom/core/tests/test_revocation_windows.py::test_issuer_that_saw_the_mark_mints_past_the_margin
sandloom/core/tests/test_revocation_windows.py::test_tokens_minted_later_in_the_revoking_request_stay_valid
G-REVOKE-03 The revocation mark is written with the cache's atomic set_max and never moves backwards; concurrent revocations of one subject all succeed and end at the maximum; a security cache with neither set_max nor cas is refused for revocation writes. sandloom/core/tests/test_auth_rotation_revocation.py::test_revocation_mark_never_moves_backwards
sandloom/core/tests/test_cache_set_max.py::test_concurrent_revocations_all_succeed_and_keep_the_max
sandloom/core/tests/test_cache_set_max.py::test_revocation_refuses_a_cache_without_set_max_or_cas
tests/integration/test_revocation_pipeline.py::test_b_concurrent_revocations_on_real_redis_keep_the_highest_mark
G-REVOKE-04 revoke_user_tokens_on_commit writes the mark only after the UoW commits: a rolled-back change never revokes (logs out) the user. sandloom_tenancy/tests/test_security_hardening.py::test_rolled_back_member_removal_skips_the_post_commit_revocation
sandloom_tenancy/tests/test_security_hardening.py::test_rolled_back_org_deletion_skips_the_post_commit_revocation
G-REVOKE-05 require_immediate=True writes the mark before COMMIT and, if that write fails, refuses the change with 503 (RevocationUnavailableError) and rolls it back; after commit a second mark max(mark, commit time + skew) revokes tokens minted between the early mark and the commit, and a commit slower than the skew also revokes the request's own tokens (counted auth_revocation_remark_overtook_floor_total). sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_immediate_revocation_writes_the_mark_before_commit
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_immediate_revocation_refuses_the_change_on_a_cache_outage
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_immediate_post_commit_remark_revokes_tokens_minted_before_commit
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_a_commit_slower_than_the_skew_revokes_the_requests_own_tokens_loudly
tests/integration/test_revocation_pipeline.py::test_window_immediate_path_is_zero_or_the_change_is_refused
G-REVOKE-06 Every bundled security event revokes the subject's tokens with require_immediate=True: tenancy member removal/leave, role change, ownership transfer (both parties), org deletion; identity role revocation, password change/reset, deactivation; block_user. sandloom_tenancy/tests/test_role_change_revocation.py::test_role_change_revokes_the_members_tokens
sandloom_tenancy/tests/test_role_change_revocation.py::test_ownership_transfer_revokes_both_parties
sandloom_tenancy/tests/test_security_hardening.py::test_member_removal_and_leave_revoke_the_users_tokens
sandloom_tenancy/tests/test_security_hardening.py::test_org_deletion_revokes_every_members_tokens
sandloom_identity/tests/api/test_security_api.py::test_role_revocation_is_immediate_on_identity_endpoints
tests/integration/test_revocation_pipeline.py::test_a_immediate_removal_needs_no_applier_after_a_crash
gap (S): No test pins that password change/reset and user deactivation use the immediate (pre-commit) path specifically, rather than only that tokens end up revoked.
G-REVOKE-07 Revocation is at-least-once: core.auth.tokens_revoked.v1 is written to the outbox in the same UoW, the post-commit mark write is retried AUTH_REVOCATION_WRITE_ATTEMPTS times, a still-failing write is counted (auth_revocation_deferred_total) and identity's durable consumer sessions@tokens_revoked applies it idempotently without ever lowering a newer mark. sandloom/core/tests/test_revocation_windows.py::test_revocation_is_persisted_in_the_uow_for_the_durable_applier
sandloom/core/tests/test_revocation_windows.py::test_exhausted_retries_count_a_deferred_revocation
sandloom_identity/tests/test_revocation_applier.py::test_applier_never_moves_a_newer_mark_backwards
tests/integration/test_revocation_pipeline.py::test_a_crash_after_a_failed_mark_write_is_applied_from_the_outbox
G-REVOKE-08 The revocation applier is registered dead_letter=False: an outage is retried forever with capped backoff (never parked in the DLQ), only a malformed payload is dead-lettered at once, and while a revocation is stuck /ready answers 503 degraded with /health unaffected. sandloom/core/tests/test_revocation_never_dead_lettered.py::test_streams_outage_longer_than_the_old_budget_is_applied_not_dlqd
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_streams_malformed_revocation_is_dead_lettered_at_once
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_ready_degrades_while_a_revocation_is_stuck_liveness_unaffected
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_redis_streams_never_dead_letter_group_survives_the_old_budget
G-REVOKE-09 The mark lives AUTH_REVOCATION_MARK_TTL_SECONDS (default max(access TTL + 120 s, 24 h), never shorter than the access lifetime); a token older than the verifier's mark TTL is rejected (fail closed on lifetime drift) and /ready fails on ACCESS_TOKEN_EXPIRE_MINUTES drift between services. sandloom/core/tests/test_auth_rotation_revocation.py::test_revocation_mark_outlives_other_services_tokens_by_default
sandloom/core/tests/test_auth_rotation_revocation.py::test_revocation_mark_ttl_setting_is_honoured_but_never_too_short
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_a_token_older_than_the_mark_ttl_is_rejected_when_no_mark_exists
sandloom/core/tests/test_contract_compat.py::test_ready_fails_on_access_token_lifetime_drift
G-REVOKE-10 A subject-state record (sandloom:auth:subject:{id}) that exists but is not a valid value rejects every token of the subject on every authenticated route (one live GET, counted); the issuance check repairs it to now + skew, never deleting or lowering a readable record, and a revocation overwrites a corrupt record. sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_corrupt_mark_rejects_every_token_and_is_counted
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_corrupt_mark_rejects_on_budgeted_reads_too
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_issuance_check_repairs_a_corrupt_mark_so_relogin_works
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_a_revocation_overwrites_a_corrupt_mark
sandloom/core/tests/test_subject_record.py::test_a_corrupt_record_fails_closed_and_issuance_repairs_it
G-REVOKE-11 On a revocation-store outage AUTH_REVOCATION_FAIL_CLOSED decides (unset = closed in prod, open elsewhere): closed answers 503 on every authenticated route (the block cannot be confirmed, no copy answers); open accepts with a warning. sandloom/core/tests/test_auth_rotation_revocation.py::test_revocation_fail_closed_defaults_by_environment
sandloom/core/tests/test_revocation_freshness.py::test_redis_down_fail_closed
sandloom/core/tests/test_revocation_freshness.py::test_redis_down_fail_open_outside_prod
sandloom/core/tests/test_revocation_freshness.py::test_copy_expiring_during_block_read_fails_closed_on_outage
G-REVOKE-12 Revocation is strict on every authenticated route: each request reads the subject-state record live with one GET, so a revocation applies on the very next request in every process; several guards on one request share the one lookup. sandloom/core/tests/test_revocation_freshness.py::test_strict_default_sees_revocation_on_next_request
tests/integration/test_revocation_pipeline.py::test_c_in_flight_request_completes_and_the_next_one_is_rejected
sandloom/core/tests/test_auth_rotation_revocation.py::test_revocation_lookup_runs_once_per_request
sandloom/core/tests/test_subject_record.py::test_the_hot_path_reads_one_key
G-REVOKE-17 before_commit(callback) runs after the handler right before COMMIT and a raising check rolls the unit back; the commit-time re-check (subject_commit_check) runs before the COMMIT of every committing unit of the request, automatically on writes and writing GETs and under at_commit() for explicit commit obligations (replacing revocation_at_commit()); it re-reads the caller's subject-state record live there, so a revocation or block written during the request answers 401 with nothing committed (also for named-database units). sandloom/core/tests/test_before_commit.py::test_a_failing_check_rolls_back_instead_of_committing
sandloom/core/tests/test_before_commit.py::test_subject_check_sees_a_block_written_during_the_request
sandloom_tenancy/tests/test_accept_authority_at_commit.py::test_block_while_accepting_rolls_the_acceptance_back
sandloom/core/tests/test_kernel_review_regressions.py::test_revocation_at_commit_covers_named_db_in_either_dependency_order
tests/integration/test_route_runtime.py::test_a_block_during_a_write_that_declares_nothing_refuses_and_rolls_back
tests/integration/test_route_runtime.py::test_a_get_that_writes_is_rechecked_and_a_reading_get_is_not
tests/integration/test_route_runtime.py::test_a_foreign_revocation_between_two_commits_refuses_the_second
G-REVOKE-18 While the commit-time re-check is armed, internal RPC sends described read methods unchanged and propagates remote commands (and methods of unknown kind) with the re-check in the MAC-covered context (rc: sub, iat_us; the provider re-checks in its own unit before COMMIT) instead of refusing them, so no remote command commits unchecked; every HTTP request (incl. nested ASGI and internal RPC) starts a fresh authority context and restores the caller's on return. sandloom/core/tests/test_security_review_regressions.py::test_guarded_request_never_sends_remote_command
sandloom/core/tests/test_security_review_regressions.py::test_guarded_request_can_still_send_described_remote_read
sandloom/core/tests/test_security_review_regressions.py::test_rpc_http_boundary_isolates_and_restores_outer_commit_authority
sandloom/core/tests/test_security_review_regressions.py::test_new_http_request_does_not_inherit_outer_issue_floors
G-REVOKE-19 Revocation marks and blocks are read and written only through SECURITY_CACHE_INSTANCE and never move to IDEMPOTENCY_CACHE_INSTANCE. sandloom/core/tests/test_production_profile.py::test_security_state_uses_the_security_cache_instance
sandloom/core/tests/test_idempotency_cache_instance.py::test_revocation_marks_stay_in_the_security_cache
G-REVOKE-21 Every authenticated route reads the subject-state record sandloom:auth:subject:{id} live, with one GET memoised per request (no route-level freshness setting, no per-process copy); a revoked or blocked token answers 401 WWW-Authenticate: Bearer error="invalid_token", a fail-closed outage 503. sandloom/core/tests/test_subject_record.py::test_the_hot_path_reads_one_key
sandloom/core/tests/test_revocation_freshness.py::test_strict_default_sees_revocation_on_next_request
sandloom/core/tests/test_revocation_freshness.py::test_revocation_in_one_process_invalidates_another
sandloom/core/tests/test_revocation_freshness.py::test_the_freshness_knobs_are_gone
tests/integration/test_platform_routes_strict.py::test_every_authenticated_platform_route_refuses_a_withdrawn_session
tests/perf_budgets/test_cache_round_trips.py::test_authenticated_get_budget
G-REVOKE-22 The commit-time re-check runs before the COMMIT of every committing unit of an armed request: automatically on user writes (a write or a queued effect commits), on a GET whose unit wrote (its on_commit kept), and on every unit under at_commit(); units that commit nothing skip it; a foreign revocation between two commits of one request refuses the second; operation-intent routes use release_check. tests/integration/test_route_runtime.py::test_a_block_during_a_write_that_declares_nothing_refuses_and_rolls_back
tests/integration/test_route_runtime.py::test_a_get_that_writes_is_rechecked_and_a_reading_get_is_not
tests/integration/test_route_runtime.py::test_a_foreign_revocation_between_two_commits_refuses_the_second
sandloom/core/tests/test_kernel_review_regressions.py::test_revocation_at_commit_covers_named_db_in_either_dependency_order
sandloom/core/tests/test_kernel_review_regressions.py::test_nested_http_request_has_independent_commit_authority
sandloom/core/tests/test_kernel_review_regressions.py::test_reused_session_does_not_keep_an_old_requests_authority_check
G-REVOKE-23 Self-revocation is "check, then revoke": a unit revoking its own subject commits after the live check passed; a foreign revocation landing before the self-revoking call is still seen; a self-revocation inside a rolled-back savepoint contributes nothing; committed units carry the exemption to later units of the request, a remote command's included: the acknowledgement crosses out as a signed caller fact (usr.sr) and the provider's re-check skips the subject exactly as a local unit's does; over RPC the provider's self-revocations come back MAC-bound (sr); a replay of the self-revoking command (the same caller's retry, locally or the proxy's automatic retry) acknowledges it alike (kept with the command's dedupe record). tests/integration/test_route_runtime.py::test_a_route_revoking_its_own_subject_commits
tests/integration/test_route_runtime.py::test_a_foreign_revocation_before_the_self_revoking_call_is_still_seen
tests/integration/test_route_runtime.py::test_a_self_revocation_in_a_rolled_back_savepoint_contributes_nothing
sandloom/core/tests/test_security_review_regressions.py::test_rpc_http_boundary_isolates_and_restores_outer_commit_authority
tests/integration/test_durable_command_idempotency.py::test_an_acknowledged_self_revocation_crosses_rpc
tests/integration/test_durable_command_idempotency.py::test_a_durable_replay_keeps_the_acknowledged_self_revocation
G-REVOKE-24 A remote command under an armed re-check is propagated in the MAC-covered RPC context (rc: sub, iat_us) and the provider re-checks before its own COMMIT (instead of the caller refusing to send it); described remote reads are sent unchanged. sandloom/core/tests/test_security_review_regressions.py::test_guarded_request_never_sends_remote_command
sandloom/core/tests/test_security_review_regressions.py::test_guarded_request_can_still_send_described_remote_read

G-BLOCK

Id Statement Enforced by
G-BLOCK-01 The block is a value of the subject-state record (v1:blocked:, no TTL), read live on every authenticated request, never from a per-process copy, and rejects every token of the user, including ones minted after the block, with 401 'Account blocked' (WWW-Authenticate: Bearer error="invalid_token", as G-REVOKE-21). sandloom/core/tests/test_revocation_freshness.py::test_block_applies_on_next_request_even_on_budget_routes
sandloom/core/tests/test_revocation_immediate_and_corrupt.py::test_any_block_value_blocks
sandloom_billing/tests/test_billing_route_classes.py::test_blocked_user_rejected_on_every_route_like_before
sandloom/core/tests/test_route_markers.py::test_differential_strict_budgeted_revoked_blocked
tests/integration/test_platform_routes_strict.py::test_every_authenticated_platform_route_refuses_a_withdrawn_session
G-BLOCK-02 block_user must run in a unit of work with a reason, writes a required audit (auth.user.blocked) and writes the block value of the subject-state record LAST (after audit, immediate revocation and event); a cache outage raises BlockUnavailableError (503) and the audit rolls back. sandloom/core/tests/test_revocation_freshness.py::test_block_requires_uow_and_reason
sandloom/core/tests/test_revocation_freshness.py::test_block_mark_is_written_last
sandloom/core/tests/test_revocation_freshness.py::test_block_write_outage_is_503_and_rolls_back_audit
G-BLOCK-03 unblock_user (audited auth.user.unblocked) replaces the block value of the subject-state record with after: by compare-and-set only after its unit of work commits (on_commit); a rolled-back or failed unblock leaves the user blocked. sandloom/core/tests/test_revocation_freshness.py::test_unblock_lifts_the_mark_only_after_commit
sandloom_identity/tests/api/test_user_safety.py::test_block_status_unblock_round_trip
sandloom/core/tests/test_subject_record.py::test_a_rolled_back_unblock_keeps_the_block
G-BLOCK-04 Every bundled token issuer (password login, password+2FA login, SSO and SSO second-factor completion, refresh exchange) calls ensure_user_not_blocked live before any side effect (no token, no refresh rotation, SSO state not consumed), answering 401 'Account blocked'; a cache outage follows AUTH_REVOCATION_FAIL_CLOSED. sandloom_identity/tests/api/test_emergency_block.py::test_blocked_user_cannot_login_or_refresh
sandloom_identity/tests/api/test_emergency_block.py::test_blocked_user_cannot_complete_sso_or_sso_2fa
sandloom_identity/tests/api/test_emergency_block.py::test_blocked_user_cannot_login_with_password_2fa
sandloom_identity/tests/api/test_emergency_block.py::test_issuer_block_check_on_cache_outage
gap (M): No generic check forces a custom (app-defined) issuer to call ensure_user_not_blocked; the rule is documentation-only for third-party issuers.
G-BLOCK-05 On commit block_user ends the user's sessions: all access tokens issued so far are revoked and core.auth.user_blocked.v1 is published, whose identity consumer revokes every refresh token, so lifting the block requires a fresh login. sandloom_identity/tests/api/test_emergency_block.py::test_block_ends_sessions_on_commit
G-BLOCK-06 One subject-state record: v1:blocked: (no TTL) outranks every v1:after:, so a revocation never unblocks; the Redis writer is one Lua script, the portable writer a compare-and-set loop that refuses to run on a store without CAS. sandloom/core/tests/test_subject_record.py::test_value_domain
sandloom/core/tests/test_subject_record.py::test_portable_order
sandloom/core/tests/test_subject_record.py::test_portable_writer_refuses_without_cas
sandloom/core/tests/test_subject_record.py::test_a_revocation_concurrent_with_a_block_never_unblocks
sandloom/core/tests/test_subject_record.py::test_redis_subject_state_order_and_ttl
sandloom/core/tests/test_subject_record.py::test_redis_concurrent_block_and_revocations_end_blocked
G-BLOCK-07 Unblock replaces blocked with after: by compare-and-set after commit and never deletes the record: tokens issued before the block stay revoked, and a rolled-back unblock keeps the block. sandloom/core/tests/test_subject_record.py::test_unblock_never_resurrects_pre_block_tokens
sandloom/core/tests/test_subject_record.py::test_a_rolled_back_unblock_keeps_the_block
sandloom/core/tests/test_subject_record.py::test_redis_unblock_swaps_and_never_deletes

G-OPCLASS

Id Statement Enforced by
G-OPCLASS-03 The platform route policy is pinned: each module's routes equal its committed routes.snapshot.json (format 3: the full resolved policy per route and method) and the kernel-mounted routes carry the kernel's policies, so adding, removing or changing the resolved policy of a route fails CI; billing's routes are exactly the approved set, declare no freshness budget and are always strict. tests/architecture/test_operation_classes.py::test_module_routes_match_their_snapshots
tests/architecture/test_operation_classes.py::test_kernel_routes_carry_the_kernel_classes
tests/architecture/test_operation_classes.py::test_a_stale_snapshot_names_the_route_and_the_fix
sandloom_billing/tests/test_billing_route_classes.py::test_every_billing_route_is_classified
sandloom_billing/tests/test_billing_route_classes.py::test_read_routes_are_strict_today_and_declare_no_budget
sandloom_billing/tests/test_billing_route_classes.py::test_financial_routes_are_always_strict
G-OPCLASS-04 create_app prunes the route-policy markers (public, no_idempotency, replay_safe, sensitive, operation_intent, writes, allow_multi_db_write) from route.dependant (never executed per request) while keeping the declaration readable; responses, OpenAPI and session behaviour are unchanged, and a marker bound to a parameter of another dependency is never pruned. sandloom/core/tests/test_route_markers.py::test_create_app_prunes_every_marker
sandloom/core/tests/test_route_markers.py::test_pruned_markers_are_no_longer_called
sandloom/core/tests/test_route_markers.py::test_openapi_is_identical
sandloom/core/tests/test_route_markers.py::test_marker_bound_to_a_parameter_is_kept
G-OPCLASS-05 Identity admin-gated RBAC / user-admin mutations re-check the actor inside the request unit (admin_in_transaction: RBAC advisory lock + actor admin assignment and active user row FOR SHARE): a token still claiming admin after revocation gets 403, a concurrent demotion serialises, and two admins demoting each other do not deadlock. sandloom_identity/tests/api/test_admin_in_transaction.py::test_admin_claim_without_the_role_cannot_administer
sandloom_identity/tests/api/test_admin_in_transaction.py::test_demotion_committing_mid_request_refuses_the_admin_action
sandloom_identity/tests/api/test_admin_in_transaction.py::test_admin_action_in_flight_serialises_the_demotion
sandloom_identity/tests/api/test_admin_in_transaction.py::test_two_admins_demoting_each_other_do_not_deadlock
G-OPCLASS-06 Tenancy admin-gated writes authorizing on the acting member (role change, removal, org update, invitation create/revoke) lock the actor row FOR SHARE and the target FOR UPDATE in the write's unit, in user_id order: a demotion/removal committed first is seen (403/404), an action committed first serialises the demotion, no deadlock. sandloom_tenancy/tests/test_actor_row_lock.py::test_demoted_actor_cannot_remove_a_member
sandloom_tenancy/tests/test_actor_row_lock.py::test_demoted_actor_cannot_invite
sandloom_tenancy/tests/test_actor_row_lock.py::test_action_committed_first_serialises_the_demotion
sandloom_tenancy/tests/test_actor_row_lock.py::test_two_admins_acting_on_each_other_do_not_deadlock
G-OPCLASS-07 Platform owner console routes: owner directory / billing / kernel per-module reads are platform-admin gated (platform admin only, tenant membership grants nothing, no secrets leaked); outbox requeue / streams replay are platform-admin gated writes (fail-closed Idempotency-Key, required audit, before-commit revocation check); credit adjustment is a money write with ledger key, balance effect and audit in one unit. sandloom_billing/tests/test_platform_admin.py::test_all_operator_reads_deny_tenant_accounts
sandloom/core/tests/test_admin_operations.py::test_views_are_operator_only_and_leak_no_secrets
sandloom/core/tests/test_admin_operations.py::test_outbox_requeue_is_audited_atomically_and_idempotent
sandloom_billing/tests/test_platform_admin.py::test_committed_adjustment_is_concurrent_safe_and_audit_failure_rolls_back

G-ROUTE

Id Statement Enforced by
G-ROUTE-01 create_app refuses (RoutePolicyError, SANDLOOM-B008) an API route that neither resolves an authenticator (@authenticates, user or kind="service") nor declares public(reason=...), one that does both, an empty reason, or two different public reasons; the error names the fix. Apps assert the same deny-by-default with sandloom.core.testing.route_policy(route). sandloom/core/tests/test_route_policy_auth.py::test_an_unauthenticated_untagged_route_is_refused_with_the_fix
sandloom/core/tests/test_route_policy_auth.py::test_public_and_an_authenticator_together_are_refused
sandloom/core/tests/test_route_policy_auth.py::test_an_empty_reason_is_refused_at_declaration
sandloom/core/tests/test_route_policy_auth.py::test_create_app_refuses_an_unauthenticated_route
sandloom/core/tests/test_route_policy_auth.py::test_every_platform_route_is_authenticated_or_public
tests/architecture/test_operation_classes.py::test_every_platform_route_has_one_consistent_class
tests/architecture/test_operation_classes.py::test_unclassified_route_fails_the_check
tests/architecture/test_operation_classes.py::test_two_different_classes_fail
examples/projecthub/tests/test_smoke.py::test_every_route_declares_its_operation_class
G-ROUTE-02 public(reason=) is recorded with its reason; token issuers (public(issues_tokens=True, reason=...)) keep the live issuance check; a router-level public() covers its routes; the bundled public routes are exactly the reviewed set. sandloom/core/tests/test_route_policy_auth.py::test_public_with_a_reason_passes_and_is_recorded
sandloom/core/tests/test_route_policy_auth.py::test_router_level_public_covers_its_routes
sandloom/core/tests/test_route_policy_auth.py::test_bundled_public_routes_are_exactly_the_reviewed_set
tests/architecture/test_operation_classes.py::test_router_level_class_covers_its_routes
G-ROUTE-03 PSP webhooks are public(reason=...) routes, not service principals, and verify their signature inside the handler (verification unchanged: an invalid or missing signature is rejected). sandloom/core/tests/test_route_policy_auth.py::test_bundled_public_routes_are_exactly_the_reviewed_set
sandloom_billing/tests/test_webhooks.py::TestWebhookSecurity::test_invalid_signature_rejected
sandloom_billing/tests/test_webhooks.py::TestWebhookSecurity::test_missing_signature_rejected
G-ROUTE-04 Each module's routes.snapshot.json (format 3) records the static resolved policy per route and method: auth, effect, session, risk with its sources, idempotency (mode), audit, step_up_max_age_s, public_reason, issues_tokens, operation_intent, write_store, multi_db_write, and authz (the custom authorization dependencies marked with sandloom.sdk.web.authorizes, in resolution order); drift, including a weakening such as a dropped risk, fails the module's tests with the fix (SANDLOOM-T001). sandloom generate never writes a weaker policy over the committed snapshot, and sandloom test never writes any generated file (authentication removed or the route made public, a risk removed, idempotency removed / no longer fail-closed / no longer durable, the session check weakened, the automatic audit dropped, step-up removed or relaxed, an authorization dependency removed): sandloom generate fails with SANDLOOM-T001 before anything is written, names sandloom routes snapshot --write, and sandloom test fails, until sandloom routes snapshot --write approves it explicitly, naming each approved weakening (the snapshot diff records it). Idempotency is compared by its properties (present, fail-closed, durable), derived from every mode the snapshot writes (none / none (reason), automatic S and automatic B (reason): a durable fail-closed record; explicit durable / cache / in-flight with or without fail-closed: only the durable kind is durable); losing any property is a weakening whatever the spelling (automatic S to explicit in-flight, fail-closed loses durability), and an unknown mode is never accepted silently. tests/architecture/test_operation_classes.py::test_module_routes_match_their_snapshots
tests/architecture/test_operation_classes.py::test_a_stale_snapshot_names_the_route_and_the_fix
tests/sdk/test_sdk_generated.py::test_every_weakened_route_policy_is_incompatible
tests/sdk/test_sdk_generated.py::test_a_route_made_public_is_incompatible
tests/sdk/test_sdk_generated.py::test_a_stronger_route_policy_is_compatible
tests/sdk/test_sdk_generated.py::test_sandloom_test_refuses_a_weakened_route_snapshot_and_the_write_approves_it
tests/sdk/test_sdk_generated.py::test_generate_refuses_a_weakened_route_and_names_the_approval
tests/sdk/test_sdk_generated.py::test_every_idempotency_mode_transition_that_loses_a_property_is_flagged
tests/sdk/test_sdk_generated.py::test_an_idempotency_mode_that_keeps_every_property_is_compatible
tests/sdk/test_sdk_generated.py::test_every_snapshot_idempotency_spelling_has_properties
tests/architecture/test_operation_classes.py::test_kernel_routes_carry_the_kernel_classes
tests/integration/test_automatic_idempotency.py::test_the_snapshot_records_the_mode
sandloom/core/tests/test_route_authorizes.py::test_the_snapshot_records_custom_authorization_dependencies
G-ROUTE-05 The money / security effects a module's own tests see on a route are recorded as INFORMATIONAL observed_effects in its routes.snapshot.json: the route runtime hands each request's risk effects to the test harness's observer (none in production; a failing observer never fails a request), the sandloom.sdk.testing plugin records them per module when SANDLOOM_TEST_OBSERVE_EFFECTS is set (sandloom test outside --ci), sandloom routes snapshot --write attaches them, and the snapshot check ignores them in either direction while a real policy change still fails. sandloom/core/tests/test_observed_effects.py::test_the_runtime_reports_a_requests_effects_to_the_observer
sandloom/core/tests/test_observed_effects.py::test_observed_effects_are_written_but_never_checked
sandloom/core/tests/test_observed_effects.py::test_the_harness_records_per_module_and_sandloom_test_opts_in

G-SENS

Id Statement Enforced by
G-SENS-01 The framework's admin gates carry the admin risk statically: require_platform_admin, require(Role("admin")), require(OrgRole("admin"|"owner")) (and an Or of them), tenancy's require_org_role("admin"|"owner") and the platform console's operator gate; member gates and Not do not. tests/integration/test_sensitive_risk.py::test_admin_gates_carry_the_admin_risk_and_member_gates_do_not
tests/integration/test_sensitive_risk.py::test_every_platform_route_behind_an_admin_gate_resolves_the_admin_risk
tests/architecture/test_operation_classes.py::test_kernel_routes_carry_the_kernel_classes
G-SENS-02 Money risk is declared at its source: @command(..., risk="money") (the L2 contract_method(command=True, risk="money")) (recorded in the describe document and the contract snapshot; billing credits.spend), the MoneyLedger mixin (a validated NOT NULL + UNIQUE operation key; billing's credit ledger; an INSERT in a request is a money effect) and @sensitive_effect("money") (billing intents). tests/integration/test_sensitive_risk.py::test_a_money_command_records_its_risk_in_the_contract_schema
tests/integration/test_sensitive_risk.py::test_a_money_ledger_must_name_a_not_null_unique_operation_key
tests/integration/test_sensitive_risk.py::test_a_money_route_commits_only_with_a_deduped_money_effect_or_a_key
sandloom/core/tests/test_command_risk_parity.py::test_a_money_command_marks_the_callers_request_locally_and_remotely
G-SENS-03 Security risk: identity's credential services (password change, 2FA init / enable / disable / backup-code regeneration, SSO link / unlink) are @sensitive_effect("security", step_up=300); mark_reauthenticated() after the current-password check (or a recent auth_time) satisfies it, so identity's behaviour is unchanged. The step-up is enforced identically when the effect runs in a contract command served over RPC: the signed caller facts carry the end user's auth_time and the request's re-authentication to the provider, whose call runs in a request scope built from them (with or without a commit-time re-check context, on an operation-intent route too), so stale auth is 401 and fresh auth or a re-authentication passes, local and remote alike; a re-authentication or a pending step-up the remote execution leaves comes back with its response. tests/integration/test_sensitive_risk.py::test_a_security_effect_needs_a_recent_auth_time_or_a_reauthentication
tests/integration/test_rpc_caller_parity.py::test_step_up_is_enforced_alike_local_and_split
tests/integration/test_rpc_caller_parity.py::test_a_reauthentication_in_a_remote_command_reaches_the_request
tests/integration/test_rpc_caller_parity.py::test_a_split_call_matches_the_local_one
G-SENS-04 Every sensitive write gets exactly one automatic, mandatory audit row (sandloom.sensitive_write) in the request's writing unit: actor, route template, method, path parameters, risks with their sources and the route's status; never the body; no row for a read or a refused request; a money command marks the caller's request alike locally and remotely (from the describe document before the call, and from the MAC-bound risks of the response: every risk the remote execution observed, its nested calls' included, so an untagged command calling a money command marks the request as it would locally; the risks travel on error responses too, merged before the caller raises, so a wrapper that raises after its nested money command committed still marks the request; a command REPLAY, local or remote (the proxy's automatic retry after a lost answer included), marks the caller exactly like its first execution: the durable command record (and the cache fallback) keeps the risks the execution observed and the replay re-applies them, its on_commit callbacks' included (round 5: the cache record is written after the callbacks ran; the durable record commits with the body's effects plus what each queued callback declares - a @sensitive_effect callback's risk and step-up, fail closed - and is corrected with what the callbacks actually left once they ran, a closure's included; a process death between the commit and that correction replays the declared callbacks' risks and nothing of a closure; round 6: the correction - and the cache record - is written on EVERY exit after the commit, a cancelled callback (a request timeout) included, with what the callbacks left until then plus what the queued ones declare when they did not all finish (fail closed), in a task shielded from the caller's cancellation (anyio cancels again at every await), bounded (5 s) and never raising into the caller: a closure that completed a money effect before its request was cancelled is on the record, so a later replay owes the audit too)), so the audit does not change after a split or a retry; such a command commits in its own unit, as does a risky effect run inside any contract unit (locally too), so the request writes its row even when its own units wrote nothing (the route runtime opens the request unit when the handler opened none). tests/integration/test_sensitive_risk.py::test_a_sensitive_write_gets_one_audit_row_and_a_read_or_failure_none
tests/integration/test_sensitive_risk.py::test_injected_session_keeps_risk_bound_through_dependency_commit
tests/integration/test_bundled_sensitive_operations.py::test_operator_revocation_reports_admin_risk_on_execution_and_replay
sandloom/core/tests/test_command_risk_parity.py::test_a_money_command_marks_the_callers_request_locally_and_remotely
tests/integration/test_sensitive_risk.py::test_a_command_only_money_route_gets_its_audit_row
sandloom/core/tests/test_command_risk_parity.py::test_a_nested_money_command_marks_the_callers_request_locally_and_remotely
tests/integration/test_rpc_caller_parity.py::test_risks_of_a_failing_command_mark_the_request_local_and_split
tests/integration/test_rpc_caller_parity.py::test_a_split_call_matches_the_local_one
tests/integration/test_rpc_caller_parity.py::test_a_replayed_command_marks_the_request_like_its_first_execution
tests/integration/test_durable_command_idempotency.py::test_a_durable_replay_marks_the_request_like_its_first_execution
tests/integration/test_durable_command_idempotency.py::test_a_durable_replay_marks_the_request_with_its_on_commit_callbacks
tests/integration/test_durable_command_idempotency.py::test_a_crash_before_the_callbacks_leaves_a_fail_closed_record
tests/integration/test_rpc_caller_parity.py::test_a_replay_marks_the_request_with_its_on_commit_callbacks
tests/integration/test_durable_command_idempotency.py::test_a_cancelled_on_commit_callback_keeps_its_effects_on_the_record
tests/integration/test_rpc_caller_parity.py::test_a_cancelled_on_commit_callback_keeps_its_effects_on_the_cache_record
sandloom/core/tests/test_contract_finish_record.py
G-SENS-05 Money dedupe is mandatory: a money effect needs a dedupe record (its own operation_key, the request's request_operation_key(), a command key or a ledger key); a client without Idempotency-Key gets 428 (SANDLOOM-R010); a route that cannot dedupe the key it got is SANDLOOM-R009 (a route whose only money effect is a command is held to the same rules: the command key is its dedupe record); a sensitive("money") write committing without either is refused before COMMIT; at startup a money route whose idempotency guard is the response-storing cache form or fail-open is refused (SANDLOOM-B019), while durable=True or store_response=False + fail_closed=True guards are accepted. tests/integration/test_sensitive_risk.py::test_injected_session_keeps_risk_bound_through_dependency_commit
tests/integration/test_sensitive_risk.py::test_a_money_effect_needs_a_dedupe_record_or_answers_428
tests/integration/test_sensitive_risk.py::test_a_key_the_route_cannot_dedupe_is_a_developer_error
tests/integration/test_sensitive_risk.py::test_a_money_route_commits_only_with_a_deduped_money_effect_or_a_key
sandloom/core/tests/test_durable_idempotency_guard.py::test_f_route_refuses_a_replay_only_guard
sandloom/core/tests/test_durable_idempotency_guard.py::test_f_route_accepts_durable_and_endpoint_owned_guards
sandloom/core/tests/test_durable_idempotency_guard.py::test_bundled_f_routes_pass_and_the_platform_wraps_no_route
tests/integration/test_sensitive_risk.py::test_a_command_only_money_route_gets_its_audit_row
G-SENS-06 Step-up (RFC 9470): an effect declaring step_up=N (security: 300 s) needs auth_time (never iat) at most N s old or mark_reauthenticated() in the request, else 401 WWW-Authenticate: Bearer error="insufficient_user_authentication", max_age=N (SANDLOOM-R011); checked at entry, at the effect's return and before the writing unit's COMMIT; a sensitive("security") route refuses before the response, reads included. tests/integration/test_sensitive_risk.py::test_a_security_effect_needs_a_recent_auth_time_or_a_reauthentication
tests/integration/test_sensitive_risk.py::test_a_security_route_refuses_before_the_response_even_on_a_read
G-SENS-07 Risk is a join: Requirement.risks() is the union over And / Or and empty under Not; a route's resolved risk is the union of its sources (admin gates, @sensitive_effect dependencies, sensitive() markers) whatever their order or repetition, each naming its sources; the derived step-up is the strictest of its contributors' (obligations are a homomorphism). tests/laws/test_risk_join_laws.py::test_requirement_risks_are_a_union
tests/laws/test_risk_join_laws.py::test_route_risk_is_the_join_of_its_sources
tests/laws/test_risk_join_laws.py::test_obligations_are_a_homomorphism

G-AUTHZ

Id Statement Enforced by
G-AUTHZ-01 Authorization decisions are three-valued (allow / deny / indeterminate) and keep their status: a definite Subscribed/Credits denial (of= an explicit Account) is 402, an unregistered or unavailable fact or a missing contract is 501, an unreachable owner is 503; an indeterminate branch never yields allow through Or/Not. tests/integration/test_shared_authz_review.py::test_http_subscribed_denial_is_402
tests/integration/test_shared_authz_review.py::test_http_unregistered_fact_is_501_not_403
tests/integration/test_shared_authz_review.py::test_or_with_erroring_branch_does_not_allow
tests/integration/test_shared_authz_review.py::test_double_negation_of_error_still_denies
tests/integration/test_access_facts_split.py::test_unreachable_owner_is_503
G-AUTHZ-02 require(...) evaluates the cheapest tier first (claims -> resource -> live) and short-circuits; a claims-only policy such as require(Subscribed(of=Account.user())) opens no DB session and does no I/O. tests/integration/test_shared_authz_review.py::test_http_claims_only_policy_opens_no_db_session
tests/integration/test_authz_algebra.py::test_cheap_claim_short_circuits_before_resource_load
tests/integration/test_access_subsystem.py::test_and_returns_first_failing_cheapest_first
G-AUTHZ-03 SubscriptionActive(of=...) and Credits(n, of=...) (of= required) are live and authoritative, resolved through the owner's contract (local, or the SERVICE_BINDINGS RPC proxy when remote). tests/integration/test_shared_authz_review.py::test_credits_uses_remote_binding_proxy
tests/integration/test_shared_authz_review.py::test_subscription_active_uses_remote_binding_proxy
tests/integration/test_shared_authz_review.py::test_http_credits_denial_is_402_with_reason_detail
G-AUTHZ-04 Registering an access fact auto-exposes .facts.v1; a process without the owner that binds it in SERVICE_BINDINGS gets the same decision as the monolith, with the declarative ClaimRule travelling over RPC (describe, cached). tests/integration/test_access_facts_split.py::test_entitled_subscription_same_decision_in_split
tests/integration/test_access_facts_split.py::test_owner_settings_drive_the_remote_claim_rule
tests/integration/test_access_facts_split.py::test_describe_is_declarative_and_cached
tests/integration/test_access_facts_split.py::test_unbound_fact_is_501_not_500
G-AUTHZ-05 Live adapters are consumer-side configuration: a registered adapter overrides the owner's contract, and registering one on the provider side warns. tests/integration/test_shared_authz_review.py::test_registered_adapter_overrides_contract
tests/integration/test_shared_authz_review.py::test_provider_side_live_adapter_registration_warns
G-AUTHZ-06 require(...) as a route dependency rejects resource-loading policies (only claim and live policies are allowed there). tests/integration/test_authz_algebra.py::test_require_rejects_resource_policy
tests/integration/test_authz_algebra.py::test_require_allows_claim_and_live_policies
G-AUTHZ-07 Org-scoped billing facts take the organization from the path or the bound tenant, never from the user id; without an org context the decision is indeterminate and a missing org can never be negated into allow. tests/integration/test_shared_authz_review.py::test_org_credits_use_org_from_path_never_user_id
tests/integration/test_shared_authz_review.py::test_org_credits_use_bound_tenant
tests/integration/test_shared_authz_review.py::test_org_credits_without_org_context_is_indeterminate
tests/integration/test_shared_authz_review.py::test_org_role_missing_org_cannot_be_negated_into_allow
G-AUTHZ-08 Authorization denials are recorded through the structured deny log and access_denied_* metrics (a denied request rolls back, so it is not audited). tests/integration/test_access_subsystem.py::test_enforce_denies_and_records_metric
G-AUTHZ-09 Subscribed, SubscriptionActive, Credits, Feature and WithinLimit require of=; a missing or non-Account value raises AuthzSubjectMissingError (a TypeError, SANDLOOM-B017) naming the three choices (Account.user(), Account.org(param=), Account.bound_tenant()). tests/integration/test_authz_subjects.py::test_every_billing_atom_requires_of
G-AUTHZ-10 Account.org(param=) resolves the path, then the query parameter, else X-Tenant-Id, else the bound tenant, validated as before; no organization, or the caller's own personal tenant, is indeterminate and never the caller's user id; Account.user() is the caller's own account on any route. Pinned by a property over random route shapes and bindings. tests/integration/test_authz_subjects.py::test_route_shapes_resolve_the_stated_account_only
tests/integration/test_authz_subjects.py::test_no_resolution_returns_a_foreign_account
examples/helpdesk/helpdesk/modules/tickets/tests/test_tickets_api.py::TestTickets::test_export_requires_subscription
G-AUTHZ-11 Account.bound_tenant() resolves by tenant kind: personal is the user, an organization confirmed by the membership contract is the org, canonical / unknown / unbound is indeterminate; tenant_kind() is authoritative only and memoised per request. tests/integration/test_authz_subjects.py::test_bound_tenant_resolves_by_tenant_kind
tests/integration/test_authz_subjects.py::test_tenant_kind_is_authoritative_and_memoised
G-AUTHZ-12 Feature / WithinLimit with an organization account read the owner's organization.features / organization.limits facts live (501 without a registered fact); with Account.user() they read the claims as before. tests/integration/test_authz_subjects.py::test_org_feature_and_limit_need_the_owners_fact
G-AUTHZ-13 The authorization algebra is unchanged (spec §8.3), pinned by properties over random And / Or / Not trees of fixed-outcome, evaluation-counting atoms against an independent model of today's rule: the value, the atoms evaluated and their order (cheapest tier first, short-circuit, no extra evaluation), fail-closed soundness (an allow holds under every two-valued completion of the indeterminate atoms), Not(U) = U, Or allowing on a genuine allow beside an indeterminate, evaluate agreeing with decide; the known order dependence (~(F & U) allows, ~(U & F) does not) is kept. tests/laws/test_authz_algebra_laws.py::test_value_and_evaluations_equal_the_documented_rule
tests/laws/test_authz_algebra_laws.py::test_an_allow_holds_under_every_completion_of_the_indeterminate
tests/laws/test_authz_algebra_laws.py::test_not_keeps_an_indeterminate
tests/laws/test_authz_algebra_laws.py::test_or_allows_on_a_genuine_allow_beside_an_indeterminate
tests/laws/test_authz_algebra_laws.py::test_evaluate_agrees_with_decide
tests/laws/test_authz_algebra_laws.py::test_the_known_order_dependence_is_kept

G-GUARD

Id Statement Enforced by
G-GUARD-01 create_app refuses (GuardOrderError) a route where FastAPI resolves an idempotency_guard before its authenticating dependency; router-level auth counts as preceding, and a custom authenticator counts once marked @authenticates. sandloom/core/tests/test_guard_order.py::test_guard_in_route_dependencies_before_param_auth_is_refused
sandloom/core/tests/test_guard_order.py::test_guard_param_before_auth_param_is_refused
sandloom/core/tests/test_guard_order.py::test_custom_authenticator_counts_once_marked
sandloom/core/tests/test_guard_order.py::test_bundled_app_routes_pass
gap (S): Tests call the order checker directly; no test boots create_app with a misordered route to prove the startup wiring.
G-GUARD-02 A valid access token's revocation and block state is checked before an idempotency_guard reserves a key or replays a stored response, even when the guard is listed before the endpoint's auth dependency. sandloom/core/tests/test_security_review_regressions.py::test_replay_checks_live_auth_before_cache_lookup

G-HTTP

Id Statement Enforced by
G-HTTP-01 The sliding-window rate limiter is always enforced in prod (RATE_LIMIT_ENABLED only forces it elsewhere), blocks boundary bursts, and a 429 carries Retry-After; on a cache outage it fails open by default or answers 503 when configured fail-closed. sandloom/core/tests/test_http_rate_limit.py::test_rate_limiter_exceeds_limit
sandloom/core/tests/test_http_rate_limit.py::test_rate_limiter_bypassed_outside_prod
sandloom/core/tests/test_client_ip.py::test_sliding_window_blocks_boundary_burst
sandloom/core/tests/test_http_rate_limit.py::test_rate_limiter_fail_closed_returns_503_on_cache_outage
G-HTTP-02 The global per-IP floor (RATE_LIMIT_GLOBAL_REQUESTS) is off at 0 by default, when set limits every route including unknown routes / 404s per IP, and fails open on a cache outage. sandloom/core/tests/test_http_rate_limit.py::test_global_floor_is_off_by_default
sandloom/core/tests/test_http_rate_limit.py::test_global_floor_limits_unknown_routes_per_ip
sandloom/core/tests/test_http_rate_limit.py::test_global_floor_fails_open_on_cache_outage
G-HTTP-03 X-Forwarded-For is honoured only with TRUST_PROXY_HEADERS=true and an immediate peer inside TRUSTED_PROXY_IPS, walked right-to-left skipping trusted hops (duplicate XFF lines combined in wire order); with trust on but no CIDRs prod uses the peer and dev the rightmost hop; invalid CIDRs fail settings. sandloom/core/tests/test_client_ip.py::test_spoofed_leftmost_xff_is_ignored
sandloom/core/tests/test_client_ip.py::test_walks_right_to_left_skipping_trusted_hops
sandloom/core/tests/test_client_ip.py::test_no_allowlist_prod_uses_peer_dev_uses_rightmost
sandloom/core/tests/test_security_review_regressions.py::test_duplicate_xff_lines_preserve_proxy_appended_client
G-HTTP-04 /metrics, /diagnostics/, /contracts/ and /health details are closed (404) without a valid INTERNAL_API_TOKEN in every environment, opened only by INTERNAL_ENDPOINTS_OPEN_IN_DEV outside prod; METRICS_TOKEN unlocks /metrics only. sandloom/core/tests/test_internal_auth.py::test_closed_in_dev_without_token_by_default
sandloom/core/tests/test_internal_auth.py::test_dev_opt_in_ignored_in_prod
sandloom/core/tests/test_internal_auth.py::test_metrics_token_unlocks_only_metrics
sandloom/core/tests/test_internal_auth.py::test_token_required_when_configured
G-HTTP-05 In prod INTERNAL_API_TOKEN and METRICS_TOKEN must be long and random, and METRICS_TOKEN must differ from INTERNAL_API_TOKEN (settings validation). sandloom/core/tests/test_ops_security_hardening.py::test_prod_validates_internal_and_metrics_tokens
G-HTTP-06 MAX_REQUEST_BODY_BYTES caps every request body globally with 413, chunked bodies and every Content-Length header included. sandloom/core/tests/test_ops_security_hardening.py::test_chunked_oversize_json_body_is_413
sandloom/core/tests/test_api_robustness.py::test_body_cap_fast_path_considers_every_content_length
G-HTTP-07 Interactive docs (/docs etc.) are off in prod unless DOCS_ENABLED is set. sandloom/core/tests/test_api_robustness.py::test_docs_exposed_property
G-HTTP-08 CORS follows CORS_ORIGINS; a wildcard origin disables credentials, and masked 500 responses still carry the CORS headers. sandloom/core/tests/test_review_fixes.py::test_cors_wildcard_disables_credentials
sandloom/core/tests/test_review_fixes.py::test_cors_explicit_origin_keeps_credentials
sandloom/core/tests/test_ops_security_hardening.py::test_masked_500_carries_cors_headers
G-HTTP-09 JSON API responses carry CSP default-src 'none'; frame-ancestors 'none', restricted permissions and COOP same-origin without breaking HTML/Swagger asset policy. sandloom/core/tests/test_security_review_regressions.py::test_json_security_headers_do_not_break_html_assets
G-HTTP-10 Public /health and /ready return check statuses only (module/consumer topology hidden without X-Internal-Token) and are cached for HEALTH_CACHE_TTL_SECONDS so anonymous traffic cannot fan out probes. sandloom/core/tests/test_ops_security_hardening.py::test_public_health_probes_are_cached_and_topology_hidden
G-HTTP-11 Logs redact sensitive extra keys by substring and scrub credential-shaped values from messages; Sentry events pass the same redaction. sandloom/core/tests/test_ops_security_hardening.py::test_log_extras_redacted_by_substring
sandloom/core/tests/test_ops_security_hardening.py::test_free_text_messages_are_scrubbed
sandloom/core/tests/test_ops_security_hardening.py::test_sentry_before_send_scrubs_event
G-HTTP-12 User (authenticated) routes answer Cache-Control: no-store unless the route sets its own Cache-Control. tests/integration/test_route_runtime.py::test_user_routes_are_no_store_unless_they_set_cache_control

G-BILLING

Id Statement Enforced by
G-BILLING-01 Checkout and customer-portal run as operation_intent routes, authorize on get_member_state (role + membership version) and commit a pending operation intent BEFORE any PSP call, keying the PSP call with the intent id (sandloom:intent:): an owner outage at authorize creates nothing (503), a PSP timeout leaves the intent pending (503) and a retry reuses the same PSP session, a definitive PSP 4xx fails the intent (502). sandloom_billing/tests/test_operation_intents.py::test_checkout_persists_an_issued_intent_keyed_to_the_psp_session
sandloom_billing/tests/test_operation_intents.py::test_org_checkout_records_the_authorized_membership_version
sandloom_billing/tests/test_operation_intents.py::test_psp_timeout_then_retry_reuses_the_intent_and_the_psp_session
sandloom_billing/tests/test_operation_intents.py::test_a_definitive_psp_rejection_fails_the_intent
sandloom_billing/tests/test_operation_intents.py::test_owner_unreachable_at_authorize_creates_nothing
G-BILLING-02 Right before the issue commit the final re-read, wrapped in release_check, checks the membership state (role lost 403, other change 409, owner unreachable fail closed) and the caller's subject-state record (401); any refusal revokes the pending intent (audited), expires the checkout session and never returns the URL (a portal URL is never handed out). sandloom_billing/tests/test_operation_intents.py::test_role_loss_during_checkout_revokes_the_intent_and_expires_the_session
sandloom_billing/tests/test_operation_intents.py::test_role_loss_during_portal_revokes_the_intent_and_withholds_the_url
sandloom_billing/tests/test_operation_intents.py::test_block_during_personal_checkout_revokes_and_expires
sandloom_billing/tests/test_f_class_authority.py::test_other_membership_change_during_the_psp_call_is_409
sandloom_billing/tests/test_operation_intents.py::test_owner_unreachable_at_the_recheck_revokes_and_expires
G-BILLING-03 The F-route authorization is serializable, not linearizable: a tenancy change committed before the final re-read is refused, one committed between the final re-read and the issue COMMIT is not seen (the acknowledged, pinned residual window). sandloom_billing/tests/test_operation_intents.py::test_role_change_committed_before_the_final_reread_is_refused
sandloom_billing/tests/test_operation_intents.py::test_window_role_change_after_the_final_reread_is_not_seen
G-BILLING-04 Intent lifecycle: every transition (issue, revoke, fail, reconciler) is guarded by WHERE status='pending'; issued, revoked and failed are terminal — no retry, refusal, PSP error or reconciler run changes an issued intent or replaces its session. sandloom_billing/tests/test_operation_intents.py::test_closing_an_issued_intent_is_a_no_op
sandloom_billing/tests/test_operation_intents.py::test_issuing_an_issued_intent_again_is_refused
sandloom_billing/tests/test_operation_intents.py::test_failed_recheck_on_retry_never_revokes_an_issued_intent
sandloom_billing/tests/test_intent_reconcile.py::test_reconciler_never_touches_an_issued_intent
G-BILLING-05 Operation-intent route retries never hand out a bearer URL without fresh authorization: the guard stores no response (store_response=False); an issued checkout retry re-runs the re-read (release_check) and returns the stored URL without a PSP call (403 and no URL after authority loss, 410 past the session expiry); an issued portal retry is 409. sandloom_billing/tests/test_operation_intents.py::test_retry_of_an_issued_intent_returns_its_url_without_a_psp_call
sandloom_billing/tests/test_operation_intents.py::test_demoted_manager_retrying_an_issued_checkout_gets_no_url
sandloom_billing/tests/test_operation_intents.py::test_retry_of_an_issued_portal_intent_is_refused_without_a_psp_call
sandloom_billing/tests/test_operation_intents.py::test_demoted_manager_cannot_replay_a_portal_url_with_a_new_token
sandloom_billing/tests/test_intent_reconcile.py::test_retry_of_an_issued_checkout_after_its_session_expired_is_410
G-BILLING-06 Intent ownership is enforced at application level on every path: retry lookup by actor + operation + key hash then customer and params digest; issue/close/reconcile writes by id + customer_id (+ actor) + status; the webhook gate resolves the customer from the PSP customer and requires the intent's own session. sandloom_billing/tests/test_operation_intents.py::test_another_tenants_idempotency_key_is_never_reused
sandloom_billing/tests/test_operation_intents.py::test_same_key_for_another_tenant_of_the_same_actor_is_refused
sandloom_billing/tests/test_operation_intents.py::test_webhook_naming_another_customers_intent_is_not_applied
sandloom_billing/tests/test_operation_intents.py::test_checkout_completion_must_name_the_intents_own_session
sandloom_billing/tests/test_intent_reconcile.py::test_reconciler_scopes_every_write_to_the_intents_customer
G-BILLING-07 The webhook applies a credit grant or a new subscription only for an object naming an issued intent of that customer; a completion for a revoked/pending/failed/unknown intent is not applied, audited (billing.intent.unauthorized_completion) and flagged for an operator; objects without the marker (pre-intent sessions) apply as before. sandloom_billing/tests/test_operation_intents.py::test_subscription_for_a_revoked_intent_is_not_activated
sandloom_billing/tests/test_operation_intents.py::test_credit_purchase_for_a_revoked_intent_grants_nothing
sandloom_billing/tests/test_operation_intents.py::test_subscription_for_an_unknown_intent_is_not_activated
sandloom_billing/tests/test_operation_intents.py::test_session_created_before_intents_still_activates
G-BILLING-08 A subscription or credit completion tied to an intent must match the intent's price with exactly one item of quantity 1, and one intent yields at most one subscription. sandloom_billing/tests/test_subscription_intent_match.py::test_subscription_to_another_price_than_its_intent_is_not_activated
sandloom_billing/tests/test_subscription_intent_match.py::test_one_intent_yields_at_most_one_subscription
sandloom_billing/tests/test_subscription_intent_match.py::test_subscription_with_a_quantity_above_one_is_not_activated
sandloom_billing/tests/test_subscription_intent_match.py::test_credit_completion_for_another_price_than_its_intent_is_refused
G-BILLING-09 The periodic reconciler (beat, acks_late) closes a pending intent older than BILLING_INTENT_RECONCILE_AFTER_MINUTES by its PSP marker: no session -> failed, open session -> revoked + expired, completed -> revoked + followup_at; lookup/expire failures are persisted and bounded by BILLING_INTENT_RECONCILE_MAX_ATTEMPTS, then left to an operator. sandloom_billing/tests/test_intent_reconcile.py::test_stale_pending_intent_session_is_found_by_marker_and_expired
sandloom_billing/tests/test_intent_reconcile.py::test_stale_pending_intent_without_a_psp_session_fails
sandloom_billing/tests/test_intent_reconcile.py::test_stale_pending_intent_paid_meanwhile_is_flagged_for_an_operator
sandloom_billing/tests/test_intent_reconcile.py::test_psp_lookup_outage_is_bounded_then_left_for_an_operator
sandloom_billing/tests/test_intent_reconcile.py::test_reconcile_task_is_scheduled_and_acks_late
G-BILLING-10 A failed expire compensation is counted (billing_intent_expire_failed), recorded on the intent and retried by the reconciler; the refusal stands and the webhook gate remains the backstop. sandloom_billing/tests/test_operation_intents.py::test_a_failed_expire_is_counted_and_the_gate_remains_the_backstop
sandloom_billing/tests/test_intent_reconcile.py::test_failed_compensation_is_persisted_and_retried
sandloom_billing/tests/test_intent_reconcile.py::test_failed_compensation_retry_is_bounded
G-BILLING-11 Only PSP-signed webhooks are processed: an invalid, missing or stale signature is rejected and the fake PSP always rejects unsigned/forged webhooks. sandloom_billing/tests/test_webhooks.py::TestWebhookSecurity::test_invalid_signature_rejected
sandloom_billing/tests/test_webhooks.py::TestWebhookSecurity::test_missing_signature_rejected
sandloom_billing/tests/test_webhooks.py::TestWebhookSecurity::test_stale_timestamp_rejected
sandloom_billing/tests/test_webhook_hardening.py::TestForgedWebhooks::test_forged_event_rejected_over_http
sandloom_billing/tests/test_webhook_hardening.py::TestForgedWebhooks::test_fake_without_secret_rejects_webhooks
G-BILLING-12 Webhook events are deduped by event id (a replay applies once) and unhandled event types are journaled skipped. sandloom_billing/tests/test_webhooks.py::TestWebhookProcessing::test_webhook_idempotent_replay
sandloom_billing/tests/test_webhooks.py::TestWebhookProcessing::test_unknown_event_type_ignored
G-BILLING-13 Webhook ownership and ordering: a subscription/invoice change applies only when its PSP customer owns the row, unknown Stripe statuses are refused, incomplete_expired/canceled are terminal and never revived, and a stale update never rolls state back. sandloom_billing/tests/test_webhook_ownership.py::test_invoice_paid_of_another_customer_does_not_recover_the_subscription
sandloom_billing/tests/test_webhook_ownership.py::test_subscription_of_another_customer_is_not_taken_over
sandloom_billing/tests/test_webhook_ownership.py::test_unknown_subscription_status_is_not_applied
sandloom_billing/tests/test_webhook_ownership.py::test_incomplete_expired_is_terminal
sandloom_billing/tests/test_webhook_hardening.py::TestSubscriptionOrdering::test_stale_update_does_not_roll_back
G-BILLING-14 A credit-pack checkout grants the catalog price's credits only when the PSP customer and metadata customer_id match, amount_total + currency equal the price and payment_status is paid. sandloom_billing/tests/test_webhook_hardening.py::TestCreditPackCheckout::test_grant_comes_from_catalog_not_metadata
sandloom_billing/tests/test_webhook_hardening.py::TestCreditPackCheckout::test_unpaid_completion_grants_nothing_until_async_success
sandloom_billing/tests/test_webhook_hardening.py::TestCheckoutAmountAndCustomerBinding::test_amount_or_currency_mismatch_grants_nothing
sandloom_billing/tests/test_webhook_hardening.py::TestCheckoutAmountAndCustomerBinding::test_metadata_customer_id_is_required
G-BILLING-15 A subscription without an intent marker is synced but grants entitlements only for a catalog price with BILLING_GRANT_INTENTLESS_SUBSCRIPTIONS=true (default false); a subscription to an unknown price grants nothing (price stored NULL, audited). sandloom_billing/tests/test_entitlement_intentless.py::test_intentless_subscription_is_synced_but_grants_nothing_by_default
sandloom_billing/tests/test_entitlement_intentless.py::test_intentless_subscription_grants_when_the_policy_allows
sandloom_billing/tests/test_entitlement_unknown_price.py::test_change_to_an_unknown_price_withdraws_the_entitlements
sandloom_billing/tests/test_entitlement_unknown_price.py::test_new_subscription_with_an_unknown_price_grants_nothing
G-BILLING-16 A portal-driven plan / cancel_at_period_end change with no issued portal intent of that customer in the attribution window is applied but audited billing.subscription.unattributed_change; lifecycle updates are never audited and another customer's portal intent never attributes. sandloom_billing/tests/test_portal_attribution.py::test_cancel_at_period_end_without_a_portal_intent_is_audited_not_blocked
sandloom_billing/tests/test_portal_attribution.py::test_lifecycle_update_is_never_audited
sandloom_billing/tests/test_portal_attribution.py::test_another_customers_portal_intent_does_not_attribute
G-BILLING-17 Refunds and disputes of credit packs reverse the granted credits proportionally and cumulatively, once per delivery, only for the grant's own PSP customer; a won dispute restores them. sandloom_billing/tests/test_refunds_disputes.py::test_full_refund_reverses_the_granted_credits
sandloom_billing/tests/test_refunds_disputes.py::test_partial_refunds_reverse_proportionally_and_cumulatively
sandloom_billing/tests/test_refunds_disputes.py::test_a_redelivered_refund_event_is_applied_once
sandloom_billing/tests/test_refunds_disputes.py::test_refund_from_another_psp_customer_reverses_nothing
sandloom_billing/tests/test_refunds_disputes.py::test_dispute_reverses_the_credits_and_a_won_dispute_restores_them
G-BILLING-18 BILLING_PROVIDER=stripe fails fast without STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET; prod refuses the fake PSP (critical startup task) unless the accepted risk fake_psp_in_prod is set; both deploy profiles require an explicit BILLING_PROVIDER. tests/integration/test_resilience_config.py::test_stripe_provider_requires_keys
sandloom/core/tests/test_auth_rotation_revocation.py::test_prod_rejects_explicit_fake_psp_unless_opted_in
sandloom_billing/tests/test_registration_surface.py::test_fake_psp_in_prod_adds_a_critical_startup_task
sandloom/core/tests/test_deploy_profiles.py::test_billing_provider_must_be_chosen
G-BILLING-19 The checkout and portal routes are operation_intent routes and take no request unit: each intent step is its own short sequential unit, no transaction is nested in another, and none (nor any held connection, checkout or row lock) spans the PSP call (external_call) or the final re-read (release_check); write admission refuses a unit opened during either (SANDLOOM-R008), including one a racing task opens while the window is entered; a read contract already running on the request's shared read connection finishes and its unit is closed before either window opens, and a contract call already running in its own provider unit finishes before either window opens (a new one is refused while it is open), whether the call is local or remote (RPC); a remote call whose provider left an attempt unanswered (timeout, transport failure, or an answer whose MAC-bound effects the caller could not merge: too large to read, no well-formed envelope, malformed effects, an unkeyed refusal - round 5) may still hold its unit, so neither window opens in that request (fail closed, 503 SANDLOOM-R008), whichever hop made the unanswered call (a provider reports its own nested unanswered calls back, caught or not, and each caller merges them). tests/integration/test_route_runtime.py::test_the_issue_and_issued_retry_cases_pass_and_hold_no_connection
tests/architecture/test_operation_classes.py::test_module_routes_match_their_snapshots
sandloom/core/tests/test_intent_window_race.py::test_a_unit_racing_the_window_entry_is_refused
sandloom/core/tests/test_intent_window_race.py::test_a_unit_left_open_by_the_drain_refuses_the_window
sandloom/core/tests/test_contract_read_scope.py::test_an_intent_window_waits_for_a_running_read_and_closes_its_unit
sandloom/core/tests/test_contract_read_scope.py::test_an_intent_window_never_runs_beside_a_running_provider_unit
sandloom/core/tests/test_rpc_intent_window.py::test_a_remote_command_inside_an_intent_window_is_refused
sandloom/core/tests/test_rpc_intent_window.py::test_an_intent_window_waits_for_a_running_remote_command
tests/integration/test_rpc_caller_parity.py::test_no_window_opens_while_a_timed_out_remote_command_may_run
tests/integration/test_rpc_caller_parity.py::test_a_nested_remote_timeout_keeps_the_root_in_doubt
tests/integration/test_rpc_caller_parity.py::test_an_oversized_answer_keeps_the_nested_doubt
tests/integration/test_rpc_caller_parity.py::test_an_answer_whose_effects_were_not_merged_is_in_doubt
G-BILLING-20 billing.credits.v1.spend / get_balance hold a personal account to the end user of the call when the call context carries one (the request's subject locally, the signed caller facts over RPC, carried on by every hop: a provider's own nested calls run for the same end user, with or without a commit-time re-check context, so splitting a wrapper never turns a user call into a system call): another user's id is BillingTenantMismatchError (403); system and service callers (no end user anywhere up the chain) are unchanged. A command replay never bypasses it: the command dedupe record is scoped to the calling end user, so another user reusing a spender's operation key runs the body and gets its own 403, never the stored result (every @command, locally and over RPC); the spender's own retry still replays. tests/integration/test_authz_subjects.py::test_a_personal_spend_must_be_the_callers_own
tests/integration/test_durable_command_idempotency.py::test_a_replay_never_serves_another_callers_result
tests/integration/test_durable_command_idempotency.py::test_a_personal_spend_replay_is_the_spenders_only
tests/integration/test_rpc_caller_parity.py::test_the_end_user_crosses_every_hop
tests/integration/test_rpc_caller_parity.py::test_a_split_call_matches_the_local_one
G-BILLING-21 The billing API's account is chosen explicitly: without org_id the personal account answers, also for a user in exactly one or several organizations (no single-organization inference); an organization's account needs org_id. sandloom_billing/tests/test_billing_api.py::TestOrgContext::test_single_org_implies_org_context
sandloom_billing/tests/test_billing_api.py::TestOrgContext::test_multiple_orgs_without_org_id_422
G-BILLING-22 A credit spend of 0 (a free item: a zero price, a promo code) succeeds and is recorded like any spend: a ledger row with delta 0 under its operation key (a replay returns the same result, the same key with another amount is 409), the spent event, the balance unchanged; an account without a billing customer yet gets one. It succeeds whatever the balance, including a negative one left by a refund or dispute reversal (a positive spend still needs cover). A negative amount is refused (422) and a grant of 0 stays refused. sandloom_billing/tests/test_credits.py::TestCreditIdempotencyHardening::test_a_zero_spend_is_recorded_and_idempotent
sandloom_billing/tests/test_credits.py::TestCreditIdempotencyHardening::test_a_zero_spend_needs_no_prior_billing_customer
sandloom_billing/tests/test_credits.py::TestCreditIdempotencyHardening::test_a_zero_spend_succeeds_on_a_negative_balance
sandloom_billing/tests/test_credits.py::TestCreditIdempotencyHardening::test_amount_bounds

G-CONTRACT

Id Statement Enforced by
G-CONTRACT-01 Every contract call, local or served over RPC, runs in the PROVIDER's own Unit of Work on the provider module's database (never the caller's session, which is restored afterwards); a read contract's transaction is always rolled back and a command contract commits its own UoW. sandloom/core/tests/test_contract_runtime.py::test_local_call_runs_in_providers_own_uow_on_its_own_database
sandloom/core/tests/test_contract_runtime.py::test_command_commits_and_requires_idempotency_key
sandloom/core/tests/test_service_rpc.py::test_rpc_server_runs_contract_in_provider_uow
sandloom/core/tests/test_contract_runtime.py::test_local_and_rpc_paths_are_identical
G-CONTRACT-02 A read contract that publishes an event, writes an audit/outbox row, queues an on_commit effect, adds/modifies ORM objects or runs raw DML raises ReadContractWriteError (in its own UoW and inside a shared read scope) and its writes never persist. sandloom/core/tests/test_contract_runtime.py::test_read_contract_cannot_publish
tests/integration/test_read_contract_writes.py::test_read_contract_write_fails_loudly
tests/integration/test_read_contract_writes.py::test_pure_read_still_succeeds
G-CONTRACT-03 A command contract must name a mandatory idempotency-key argument at registration, and a call with an empty key is refused (ContractIdempotencyKeyMissing) before any UoW opens. sandloom/core/tests/test_contract_runtime.py::test_command_contract_must_name_its_idempotency_key
sandloom/core/tests/test_contract_runtime.py::test_command_commits_and_requires_idempotency_key
G-CONTRACT-04 Contract signatures take no session: a provider method with a session parameter is refused at registration (and by the architecture test), and a session passed by a caller, positionally or by keyword, is 422 ContractArgumentError before any UoW opens, locally and over RPC. sandloom/core/tests/test_contract_runtime.py::test_caller_session_is_an_argument_error
sandloom/core/tests/test_service_rpc.py::test_a_session_taking_provider_is_refused_at_registration
sandloom/core/tests/test_service_rpc.py::test_remote_proxy_decodes_uuid_and_refuses_a_session_argument
tests/architecture/test_contract_methods_no_session.py::test_registered_contracts_take_no_session
G-CONTRACT-05 register_service raises ContractTypeError for a provider with a public member that is not a @contract_method, an unannotated or non-wire-type parameter/return, or args/*kwargs; a local provider exposes only its contract methods (private members and unknown names raise AttributeError). sandloom/core/tests/test_contract_parity.py::test_non_portable_contracts_are_rejected_at_registration
sandloom/core/tests/test_contract_parity.py::test_local_provider_exposes_only_contract_methods
tests/laws/test_wire_codec_laws.py::test_a_non_wire_annotation_is_refused_wherever_it_sits
G-CONTRACT-06 Arguments bind with Python's rules identically on the local and RPC paths: unknown/typo kwargs, missing arguments, positional-only by keyword, keyword-only positionally and arguments failing annotation validation are 422 ContractArgumentError both ways. sandloom/core/tests/test_contract_parity.py::test_local_and_rpc_results_and_errors_are_identical
sandloom/core/tests/test_contract_parity.py::test_rpc_type_fidelity
sandloom/core/tests/test_contract_runtime.py::test_runtime_command_binds_its_arguments_once_per_layer
G-CONTRACT-07 Results are encoded once and decoded against the return annotation on both paths, so a local caller receives exactly the types a remote one does (a pydantic model not a dict, an Enum not its value, exact Decimal, UUID, tuple/set/frozenset, non-string map keys), with a stable digest independent of the hash seed; a Contract(REF) caller receives each DTO as the class its generated stub names (the generated copy, G-CONTRACT-19) on both paths, every field set as read off the wire. sandloom/core/tests/test_contract_parity.py::test_rpc_type_fidelity
tests/sdk/test_sdk_contract_dtos.py::test_a_caller_receives_the_copy_equal_to_the_providers_value
sandloom/core/tests/test_contract_parity.py::test_wire_codec_round_trips_exactly
sandloom/core/tests/test_contract_parity.py::test_set_digest_is_stable_across_hash_seeds
sandloom/core/tests/test_contract_codec_v2.py::test_local_plan_equals_v1_roundtrip
G-CONTRACT-08 SANDLOOM-C012. A business module registering a contract whose pydantic model / Enum (nested included) is a class of ANOTHER module's package, or one defined in a function or main, fails with ContractTypeError naming the DTO, the method position and the fix; a DTO of the provider's own package (the norm: its schemas module) or of a shared package (sandloom.shared.*, CONTRACT_DTO_PACKAGES) is accepted, and sandloom generate writes a copy of each own DTO into the shared DTO package. sandloom check (contract-dtos) reports an own-package DTO a contract signature (an @contract class's public method or a @contract_method) names that has no generated copy. A remote caller that has not imported a DTO's class decodes it into a loaded generated copy of it, else as plain data, and never imports a module named by the peer. sandloom/core/tests/test_contract_dto_locality.py::test_module_contract_with_own_package_dto_is_accepted
sandloom/core/tests/test_contract_dto_locality.py::test_a_function_local_dto_is_refused
sandloom/core/tests/test_contract_dto_locality.py::test_nested_enum_of_another_loaded_module_is_refused
sandloom/core/tests/test_contract_dto_locality.py::test_remote_decode_degrades_when_the_dto_is_not_importable
sandloom/core/tests/test_contract_dto_locality.py::test_remote_decode_resolves_a_loaded_generated_copy
sandloom/core/tests/test_contract_dto_locality.py::test_shared_and_declared_dto_packages_are_accepted
tests/architecture/test_archcheck_library.py::test_contract_dto_leaks_detected
rule contract-dtos
G-CONTRACT-09 A provider HTTPException or http_status-protocol exception reaches the caller as ContractCallError with the same status, detail, headers (e.g. Retry-After) and code locally AND over RPC; a domain 401/403 stays 401/403 over RPC, never a 503. sandloom/core/tests/test_contract_parity.py::test_domain_403_over_rpc_is_not_a_503
sandloom/core/tests/test_contract_parity.py::test_retry_after_header_survives_rpc
sandloom/core/tests/test_contract_parity.py::test_local_and_rpc_results_and_errors_are_identical
sandloom_billing/tests/test_contract_conformance.py::test_foreign_org_spend_is_403_not_503_over_rpc
G-CONTRACT-10 An unknown method or contract at a remote provider surfaces at the caller as 501 ServiceUnavailableInDeployment (an unknown method on a local provider is AttributeError). sandloom/core/tests/test_contract_parity.py::test_unknown_method_at_provider_is_501
sandloom/core/tests/test_contract_parity.py::test_local_provider_exposes_only_contract_methods
G-CONTRACT-11 An AttributeError / ValueError (or any unexpected exception) escaping provider code is a provider failure — 500 locally, 503 RemoteServiceUnavailable code=provider_error remotely — and is never masked as the endpoint's 404 method_not_found or 422 binding error. sandloom/core/tests/test_service_rpc.py::test_rpc_errors_inside_provider_are_not_masked_as_404_or_422
sandloom/core/tests/test_contract_parity.py::test_local_and_rpc_results_and_errors_are_identical
G-CONTRACT-12 At startup every ContractRequirement in ModuleDescriptor.consumes must be provided locally or bound in SERVICE_BINDINGS: a missing required one raises ContractBindingError (startup aborts), a missing optional one only warns, and the shipped modules declare what they consume. sandloom/core/tests/test_consumes_preflight.py::test_missing_required_consumed_contract_aborts_startup
sandloom/core/tests/test_consumes_preflight.py::test_missing_optional_consumed_contract_only_warns
sandloom/core/tests/test_consumes_preflight.py::test_provider_loaded_later_or_binding_satisfies
sandloom/core/tests/test_module_boots_alone.py::test_required_missing_contract_is_the_explicit_refusal
G-CONTRACT-13 Inside a request read scope LOCAL read-contract calls on one database share one contract-pool connection (READ COMMITTED, always rolled back) that never sees the caller's uncommitted writes; a failing call discards the shared transaction and nested/concurrent calls fall back to their own UoW. sandloom/core/tests/test_contract_read_scope.py::test_calls_in_a_scope_share_one_connection
sandloom/core/tests/test_contract_read_scope.py::test_scope_never_sees_the_callers_uncommitted_writes
sandloom/core/tests/test_contract_read_scope.py::test_failed_call_discards_the_shared_transaction
sandloom/core/tests/test_contract_read_scope.py::test_nested_and_concurrent_calls_fall_back_to_their_own_uow
G-CONTRACT-14 Identical local and RPC read calls (contract, method, bound tenant, arguments) are memoised for the request, each hit is an independent copy, the memo is keyed by the bound tenant, any command call (local or remote) clears it, and the HTTP boundary never inherits another request's memo. sandloom/core/tests/test_contract_read_scope.py::test_memo_serves_repeats_and_a_command_invalidates_it
sandloom/core/tests/test_contract_parity.py::test_external_changes_and_command_invalidation_have_same_read_policy
sandloom/core/tests/test_contract_read_scope.py::test_memo_hits_are_independent_copies
sandloom/core/tests/test_contract_read_scope.py::test_memo_is_keyed_by_bound_tenant
G-CONTRACT-15 In-process contract UoWs use a dedicated per-database pool (CONTRACT_DB_POOL_SIZE) so a nested contract call succeeds even with DB_POOL_SIZE=1, and contract connections carry lock_timeout (CONTRACT_DB_LOCK_TIMEOUT_MS) so a contract waiting on its caller's row lock fails fast instead of deadlocking. sandloom/core/tests/test_contract_pool.py::test_nested_contract_call_with_request_pool_size_one_succeeds
sandloom/core/tests/test_contract_pool.py::test_without_the_contract_pool_the_nested_call_starves
sandloom/core/tests/test_contract_pool.py::test_contract_waiting_on_callers_row_lock_fails_fast
G-CONTRACT-16 The typed wire codec v2 is used only for fully typed annotations and only once the provider is described and its contract hash matches the caller's accepted hash; any value not exactly fitting falls back to codec 1 with unchanged coercion/errors, and on the local path only immutable exact-type values skip the round trip (mutables are copied); codec 1 round-trips every wire value, local equals remote and codec 1 equals codec 2 when fully typed (properties), and an int beyond 64 bits crosses RPC tagged (bigint) so local still equals remote. sandloom/core/tests/test_contract_codec_v2.py::test_server_answers_typed_only_for_the_accepted_contract_hash
sandloom/core/tests/test_contract_codec_v2.py::test_new_client_new_server_uses_typed_codec
sandloom/core/tests/test_contract_codec_v2.py::test_local_plan_rejects_like_v1
sandloom/core/tests/test_contract_codec_v2.py::test_immutable_exact_values_pass_through_and_mutables_are_copied
tests/laws/test_wire_codec_laws.py::test_codec_1_round_trips_every_wire_value
tests/laws/test_wire_codec_laws.py::test_local_equals_remote
tests/laws/test_wire_codec_laws.py::test_codec_1_equals_codec_2_when_fully_typed
tests/laws/test_wire_codec_laws.py::test_an_int_beyond_64_bits_does_not_cross_rpc
G-CONTRACT-17 Every shipped contract (tenancy.membership.v1, billing.entitlements.v1, billing.credits.v1, billing.facts.v1, *.claims.v1) gives identical results and errors locally and over RPC under assert_contract_conformance. sandloom_tenancy/tests/test_contract_conformance.py::test_tenancy_contracts_conform
sandloom_billing/tests/test_contract_conformance.py::test_billing_contracts_conform
sandloom_billing/tests/test_facts_contract_conformance.py::test_billing_facts_contract_conforms
sandloom_identity/tests/test_contract_conformance.py::test_identity_claims_contract_conforms
G-CONTRACT-18 Read contracts keep no idempotency record on either path, and the RPC layer adds no dedupe record of its own (commands share the contract runtime's single record). sandloom/core/tests/test_contract_parity.py::test_reads_persist_no_idempotency_record
sandloom/core/tests/test_contract_parity.py::test_command_dedupe_is_shared_bounded_and_size_capped
sandloom/core/tests/test_service_rpc.py::test_command_contract_is_deduped_by_the_framework_locally_and_over_rpc
G-CONTRACT-19 Contract DTOs are authored in the provider and copied for callers: sandloom generate writes a wire-shape copy of every DTO a provided contract signature names (nested ones included) into /contracts/_dto.py, each class marked @dto_copy with its origin, and the stub names the copy and passes its Protocol (ContractRef(name, Protocol)). A Contract(REF) caller - and Contract(REF, optional=True).client() - receives results as the stub annotates them (the copy's class), on the local path and over RPC (with or without the provider's package imported in the caller process), equal to what the provider returned; a frozen copy of immutable scalars is rebuilt without revalidation, anything else through one JSON round trip; a result already of the stub's class, or naming no model / Enum, is not converted. The provider receives its own class for a copy the caller passes, on both paths. tests/sdk/test_sdk_contract_dtos.py::test_a_caller_receives_the_copy_equal_to_the_providers_value
tests/sdk/test_sdk_contract_dtos.py::test_the_fast_path_copies_a_frozen_scalar_dto_without_revalidating
tests/sdk/test_sdk_contract_dtos.py::test_the_stub_names_the_copies_and_passes_its_protocol
tests/sdk/test_sdk_contract_dtos.py::test_every_copied_class_is_marked_and_carries_the_wire_shape
sandloom/core/tests/test_contract_dto_locality.py::test_remote_decode_resolves_a_loaded_generated_copy
sandloom_identity/tests/test_contact_contract.py::test_get_contact_and_get_contacts

G-COMPAT

Id Statement Enforced by
G-COMPAT-01 Each bundled module commits contracts.snapshot.json equal to the rendered describe documents of its registered contracts; a stale or missing snapshot fails the suite. For L1 modules, sandloom generate compares every generated file with the committed one before writing anything (SANDLOOM-T004 contracts, T005 events, T007 stubs, C012 DTO copies, T001 a route that lost its authentication); sandloom test, dev and --ci alike, only checks and fails on a stale file, listing it under "generated files are stale: run sandloom generate". sandloom/core/tests/test_contract_compat.py::test_committed_module_snapshots_match_registered_contracts
tests/sdk/test_sdk_generated.py::test_apply_writes_in_dev_and_fails_stale_in_ci
tests/sdk/test_sdk_generated.py::test_a_stale_file_asks_for_sandloom_generate_and_is_left_alone
sandloom/core/tests/test_run_tests_cli.py::test_sandloom_test_never_writes_a_generated_file
tests/sdk/test_sdk_generated.py::test_stub_compatibility_is_additive
tests/sdk/test_sdk_generated.py::test_a_route_that_loses_its_authentication_is_incompatible
tests/sdk/test_sdk_generated.py::test_a_generated_stub_is_ruff_formatted
G-COMPAT-02 The compatibility comparison reports as breaking a removed method/parameter, a newly required parameter, a changed position/kind/idempotency key and a narrowed parameter or incompatible result type, and passes additive changes (new methods, optional parameters/fields, widened parameter types). sandloom/core/tests/test_contract_compat.py::test_additive_changes_are_compatible
sandloom/core/tests/test_contract_compat.py::test_breaking_changes_are_reported_precisely
sandloom/core/tests/test_contract_compat.py::test_type_and_order_changes_are_reported
sandloom/core/tests/test_contract_compat.py::test_narrowing_a_parameter_type_is_breaking
G-COMPAT-03 sandloom contracts check --against <snapshot> exits 0 on additive changes and 1 on breaking ones (default --as provider), and with --as consumer reports methods the released provider lacks. sandloom/core/tests/test_contract_compat.py::test_cli_check_against_snapshot
gap (S): No test drives sandloom contracts check --against http://<provider> (live describe document over the internal token) or the sandloom contracts snapshot --write/--check CLI exit codes (only the underlying functions are tested).
G-COMPAT-04 With CONTRACT_COMPAT_CHECK=ready (default) a caller compares every reachable remote-bound provider's live describe document with the provider module's snapshot (found in its package without importing it): an incompatible provider fails /ready with contract_incompatible, an additive one passes. sandloom/core/tests/test_contract_compat.py::test_ready_fails_on_an_incompatible_provider
sandloom/core/tests/test_contract_compat.py::test_ready_passes_on_an_additive_provider
sandloom/core/tests/test_contract_compat.py::test_expectation_is_found_in_the_provider_package_without_importing_it
G-COMPAT-05 ContractRequirement(name, methods=(...)) narrows the compatibility check to the methods the consumer calls, so breaks in other methods do not fail /ready. sandloom/core/tests/test_contract_compat.py::test_methods_the_consumer_does_not_call_do_not_fail_ready
sandloom/core/tests/test_contract_compat.py::test_methods_narrow_the_check
G-COMPAT-06 CONTRACT_COMPAT_CHECK=strict aborts startup on a reachable incompatible provider, ready only logs at startup (and fails /ready), and off restores reachability-only readiness. sandloom/core/tests/test_contract_compat.py::test_startup_check_strict_raises_ready_only_logs
sandloom/core/tests/test_contract_compat.py::test_compat_check_off_restores_reachability_only
G-COMPAT-07 A provider describe document without model field information yields a warning (contracts_unverified), not an incompatibility failure. sandloom/core/tests/test_contract_compat.py::test_model_without_field_info_is_a_warning_not_an_error
gap (S): The startup log for a remote binding with no snapshot at all ("unchecked", CONTRACT_SNAPSHOT_PATHS absent) is not asserted.

G-RPC

Id Statement Enforced by
G-RPC-01 The request key K_req is HKDF-SHA256 over the X25519 agreement of caller and provider with a role-ordered, direction-labelled info naming both services and kids: both sides derive the same key, the reverse direction and swapped ids give different keys, and it is domain-separated from the response-binding key (committed test vectors pin the schedule). sandloom/core/tests/test_rpc_pair_mac.py::test_both_sides_derive_the_same_directional_pair_key
sandloom/core/tests/test_rpc_pair_mac.py::test_request_key_is_domain_separated_from_the_response_binding_key
sandloom/core/tests/test_rpc_pair_mac.py::test_swapped_ids_and_wrong_direction_key_are_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_committed_vectors
G-RPC-02 Low-order / non-canonical X25519 points and an all-zero shared secret never yield a usable pair key: they are refused at startup for every configured pair and at verification for a published caller key. sandloom/core/tests/test_rpc_pair_mac.py::test_low_order_points_never_yield_a_predictable_key
sandloom/core/tests/test_rpc_pair_mac.py::test_all_zero_agreement_is_refused_even_if_the_library_returned_it
sandloom/core/tests/test_rpc_pair_mac.py::test_low_order_published_caller_key_is_refused_at_verification
G-RPC-03 The MAC input is a fixed-arity length-prefixed (injective) encoding of caller, caller kid, provider, provider kid, HTTP method, target, nonce, raw context bytes and body SHA-256; tampering with any one field makes the request fail verification. sandloom/core/tests/test_rpc_pair_mac.py::test_encoding_is_injective_on_classic_ambiguities
sandloom/core/tests/test_rpc_pair_mac.py::test_encoding_round_trips_and_never_collides_fuzz
sandloom/core/tests/test_rpc_pair_mac.py::test_mac_input_fields_all_change_the_encoding
sandloom/core/tests/test_rpc_pair_mac.py::test_each_tampered_mac_field_is_refused
G-RPC-04 The provider verifies the single canonical X-SANDLOOM-RPC-Auth header and full 32-byte tag over the raw bytes before decoding the context or body; malformed headers, non-canonical/truncated tags, a nonce that is not 128-bit lowercase hex, unknown kids and contexts with duplicate JSON keys are all 401 rpc_auth_invalid. sandloom/core/tests/test_rpc_pair_mac.py::test_malformed_headers_and_tags_are_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_non_canonical_tag_encoding_is_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_unknown_kids_are_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_context_with_duplicate_keys_is_refused
G-RPC-05 Requests are audience-bound by construction: a request MACed for provider P never verifies at another service Q (aud must equal RPC_SERVICE_ID, the key names P), and a compromised peer cannot forge another caller's request to a third service. sandloom/core/tests/test_rpc_pair_mac.py::test_wrong_audience_is_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_captured_request_replayed_to_another_service_is_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_a_peer_cannot_forge_another_callers_request
tests/split_conformance/test_split_conformance.py::test_internal_rpc_requires_an_allowed_pair_mac
G-RPC-06 A request is fresh only when |now - iat| <= RPC_AUTH_MAX_SKEW_SECONDS (symmetric); otherwise 401 rpc_auth_expired, including out-of-range iat values (never a 500), and accepted requests skewed beyond half the window are counted per caller. sandloom/core/tests/test_rpc_pair_mac.py::test_freshness_window_is_symmetric_and_strict
sandloom/core/tests/test_rpc_pair_mac.py::test_r5_out_of_range_issue_times_are_expired_not_500
sandloom/core/tests/test_rpc_pair_mac.py::test_clock_skew_beyond_half_the_window_is_counted
G-RPC-07 The per-process nonce replay guard, consulted only after the tag verified and the caller is authorized, refuses a repeated (caller, nonce) within the window with 401 rpc_auth_replayed, keeps entries until the request could no longer be fresh, never evicts a live nonce (fails closed 503 rpc_replay_unavailable when full, never rebuilt) and caps one caller's share of the table. sandloom/core/tests/test_rpc_pair_mac.py::test_captured_request_replayed_to_the_same_provider_is_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_replay_guard_detects_expires_and_fails_closed_when_full
sandloom/core/tests/test_rpc_pair_mac.py::test_r3_an_unauthorized_peer_cannot_fill_the_replay_guard
sandloom/core/tests/test_rpc_pair_mac.py::test_one_caller_cannot_take_the_whole_nonce_table
G-RPC-08 With RPC_REPLAY_GUARD=commands (default) command calls (all calls with all) are also claimed by an atomic SET NX on the security cache under a collision-free key with a constant 3*skew TTL, so a replay landing on another process/replica is refused; a cache outage fails closed in prod and open outside it. sandloom/core/tests/test_rpc_pair_mac.py::test_shared_nonce_layer_catches_a_replay_on_another_process
sandloom/core/tests/test_rpc_pair_mac.py::test_commands_mode_uses_the_shared_layer_for_commands_only
sandloom/core/tests/test_rpc_pair_mac.py::test_shared_nonce_layer_outage_fails_closed_in_prod_only
sandloom/core/tests/test_rpc_pair_mac.py::test_r7_shared_replay_keys_never_collide
G-RPC-09 RPC_REPLAY_GUARD=local is refused in production, and outside production a startup warning says the shared nonce layer fails open. sandloom/core/tests/test_rpc_pair_mac.py::test_local_replay_guard_is_refused_in_prod
sandloom/core/tests/test_rpc_pair_mac.py::test_startup_warns_that_the_shared_guard_fails_open_outside_prod
G-RPC-10 The provider binds the tenant only from the MAC-covered X-SANDLOOM-RPC-Context; a bare X-Tenant-Id header on an RPC request is 400 rpc_unsigned_tenant, and the proxy propagates the caller's bound tenant inside the MACed context. sandloom/core/tests/test_service_rpc.py::test_rpc_server_rebinds_tenant_only_from_verified_context
sandloom/core/tests/test_rpc_pair_mac.py::test_x_tenant_id_is_refused_on_the_mac_path
sandloom/core/tests/test_service_rpc.py::test_rpc_proxy_propagates_bound_tenant_in_the_mac_covered_context
G-RPC-11 RPC_ALLOWED_CALLS is deny-by-default and consulted with the verified MAC caller id: a verified caller not allowed the contract/method gets 403 rpc_caller_forbidden at the provider; between real processes the ops token alone opens no contract and a caller is held to its own bindings. sandloom/core/tests/test_rpc_allowlist.py::test_unlisted_method_is_forbidden
sandloom/core/tests/test_rpc_allowlist.py::test_unlisted_caller_is_forbidden
sandloom/core/tests/test_rpc_allowlist.py::test_allowed_caller_succeeds
tests/split_conformance/test_split_conformance.py::test_internal_rpc_requires_an_allowed_pair_mac
G-RPC-12 A caller whose own RPC_ALLOWED_CALLS entry does not cover every contract in its SERVICE_BINDINGS refuses to start, and the shipped microservices profile's allowlist equals the one derived from its bindings. sandloom/core/tests/test_rpc_allowlist.py::test_every_bound_contract_must_be_allowed_for_the_caller
sandloom/core/tests/test_deploy_profiles.py::test_rpc_allowlist_is_derived_from_bindings
G-RPC-13 With RPC_COMMANDS_REQUIRE_SIGNATURE on, a command call (and every not-yet-described method) must also carry the MAC caller's own Ed25519 signature covering the context (incl. audience and the attempt's nonce), target and body, so a leaked provider X25519 key forges reads at most, never effects: forged, foreign-signed or re-MACed commands are refused with no effect, and all requires signatures on reads too. sandloom/core/tests/test_rpc_pair_mac.py::test_kci_a_leaked_provider_key_forges_reads_but_never_commands
sandloom/core/tests/test_rpc_pair_mac.py::test_kci_a_command_signed_by_another_service_is_refused
sandloom/core/tests/test_rpc_pair_mac.py::test_kci_a_captured_signed_command_cannot_be_re_maced_under_a_new_nonce
sandloom/core/tests/test_rpc_pair_mac.py::test_all_mode_signs_and_requires_reads_too
G-RPC-14 RPC_COMMANDS_REQUIRE_SIGNATURE=auto is on in prod and off in dev/test; off and stale (unpublished) RPC_DH_EXTRA_PRIVATE_KEYS kids are refused under multi_tenant_strict (stale kids warned otherwise), and a calling service without RPC_PRIVATE_KEY refuses to start while the requirement is on. sandloom/core/tests/test_rpc_pair_mac.py::test_commands_require_signature_defaults_and_startup_rule
sandloom/core/tests/test_rpc_pair_mac.py::test_strict_profile_refuses_unsigned_commands_and_stale_extra_keys
sandloom/core/tests/test_rpc_allowlist.py::test_signed_commands_need_the_callers_ed25519_key
G-RPC-15 Every SERVICE_BINDINGS entry must pin its provider with service_id (startup refuses otherwise), and a caller never sends a call it cannot MAC: an unpinned binding or a provider without a published X25519 key fails 503 rpc_auth_unavailable with nothing sent. sandloom/core/tests/test_rpc_allowlist.py::test_every_binding_must_pin_its_provider
sandloom/core/tests/test_rpc_pair_mac.py::test_caller_never_falls_back_without_a_pair_key
sandloom/core/tests/test_rpc_keyed_binding.py::test_an_unpinned_binding_never_sends_a_call
sandloom/core/tests/test_rpc_keyed_binding.py::test_every_binding_is_pinned_to_a_keyed_provider
G-RPC-16 A process with SERVICE_BINDINGS refuses to start without its own RPC_DH_PRIVATE_KEY, a provider without one refuses every RPC call (unkeyed), and a monolith with no bindings needs no RPC keys. sandloom/core/tests/test_rpc_allowlist.py::test_caller_with_bindings_needs_its_own_x25519_key
sandloom/core/tests/test_rpc_keyed_binding.py::test_a_caller_refuses_to_start_without_its_x25519_key
sandloom/core/tests/test_rpc_keyed_binding.py::test_provider_without_an_x25519_key_refuses_unkeyed
sandloom/core/tests/test_rpc_allowlist.py::test_a_monolith_needs_no_rpc_keys
G-RPC-17 Boot refuses unusable RPC key configurations: own X25519 key missing or not matching its published half, an Ed25519 private key not matching its published key, non-X25519 / non-Ed25519 keys in the maps, duplicate published X25519 keys, an underivable pair, and an X25519 key equal to the Ed25519 signing seed. sandloom/core/tests/test_rpc_pair_mac.py::test_startup_refuses_unusable_configurations
sandloom/core/tests/test_rpc_pair_mac.py::test_startup_refuses_a_dh_key_that_is_the_signing_seed
sandloom/core/tests/test_rpc_keyed_binding.py::test_x25519_key_configuration_is_validated
sandloom/core/tests/test_rpc_allowlist.py::test_private_key_must_be_the_published_one
G-RPC-18 Every provider-side authentication/authorization rejection (rpc_auth_, rpc_identity_, rpc_context_*, rpc_caller_forbidden, rpc_replay_unavailable) and every unkeyed error answer surfaces at the caller as 503 RemoteServiceUnavailable carrying the provider's code, never as the provider's 401/403. sandloom/core/tests/test_rpc_pair_mac.py::test_auth_failures_are_503_at_the_caller_with_a_stable_code
sandloom/core/tests/test_rpc_allowlist.py::test_proxy_of_an_unlisted_caller_gets_503
G-RPC-19 A caller accepts a response only when it echoes the attempt's nonce and carries a valid v2 pair-keyed MAC from the pinned service_id; stale, swapped, wrong-request-id, unsolicited, unbound, unkeyed, forged or other-service responses are rejected (never returned as a result, even under concurrency with random faults). sandloom/core/tests/test_rpc_response_binding.py::test_peer_returning_a_stale_response_of_another_call
sandloom/core/tests/test_rpc_response_binding.py::test_an_unbound_reply_is_rejected
sandloom/core/tests/test_rpc_keyed_binding.py::test_a_forged_response_without_the_pair_key_is_rejected
sandloom/core/tests/test_rpc_keyed_binding.py::test_pinned_to_another_service_is_refused
G-RPC-20 The response MAC covers the request's own X-SANDLOOM-RPC-Auth (and command signature), and a request carrying any auth/nonce/context/signature header twice is refused and never bound, so a provider's answer to a replayed or re-labelled request never verifies for another request. sandloom/core/tests/test_rpc_pair_mac.py::test_r1_a_response_cannot_be_rebound_to_another_request
sandloom/core/tests/test_rpc_pair_mac.py::test_r1_request_auth_covers_the_request_mac
sandloom/core/tests/test_rpc_keyed_binding.py::test_the_mac_covers_the_request_auth
G-RPC-21 Responses are keyed with an HKDF pair key over each service's DEDICATED X25519 key (never its Ed25519 signing key, no custom curve code); the provider keys call, describe and error responses only for a caller whose request MAC verified, and answers others (e.g. the 413 body cap) with the nonce alone, unkeyed; unverified callers never churn the key cache. sandloom/core/tests/test_rpc_keyed_binding.py::test_no_custom_curve_code_and_no_signing_key_reuse
sandloom/core/tests/test_rpc_response_binding.py::test_provider_binds_call_describe_and_error_responses
sandloom/core/tests/test_rpc_response_binding.py::test_the_body_cap_413_echoes_the_nonce_unkeyed
sandloom/core/tests/test_rpc_binding_key_caches.py::test_unverified_or_unknown_callers_never_churn_the_pair_key_cache
G-RPC-22 A response-binding rejection (other than an unkeyed refusal) discards the whole client connection pool and is a retryable transport error; a peer that always answers another call yields 503, never a result. sandloom/core/tests/test_rpc_response_binding.py::test_a_rejected_response_discards_the_connection_pool
sandloom/core/tests/test_rpc_response_binding.py::test_a_peer_that_always_answers_another_call_is_503_never_a_result
sandloom/core/tests/test_rpc_response_binding.py::test_concurrency_64_with_random_faults_never_returns_another_result
G-RPC-23 The aiohttp and httpx transports are behaviourally identical: both raise httpx's exception classes, pass the same fault-injection, pair-MAC and contract-conformance suites, and send pair-MACed requests with the tenant context. sandloom/core/tests/test_rpc_transports.py::test_request_on_the_wire_is_pair_maced_with_tenant_context
sandloom/core/tests/test_contract_parity.py::test_local_and_rpc_results_and_errors_are_identical
sandloom/core/tests/test_rpc_pair_mac.py::test_both_transports_authenticate_and_refuse_raw_replays
sandloom/core/tests/test_rpc_response_binding.py::test_httpx_transport_error_class_is_retryable
G-RPC-24 A response larger than RPC_MAX_RESPONSE_BYTES is 502 RemoteServiceBadResponse and is never retried. sandloom/core/tests/test_rpc_transports.py::test_oversized_response_is_502_and_not_retried
G-RPC-25 The proxy retries a transport failure exactly once under a fresh nonce and tag (never seen as a replay; a signed command is re-signed and executes once), a second failure or an unreachable peer is 503, and a timeout is 503 documenting that the call may have committed. sandloom/core/tests/test_service_rpc.py::test_rpc_proxy_retries_transport_errors_under_a_fresh_nonce
sandloom/core/tests/test_rpc_transports.py::test_peer_closing_mid_response_twice_is_503
sandloom/core/tests/test_service_rpc.py::test_rpc_timeout_is_503_and_documents_maybe_committed
sandloom/core/tests/test_rpc_pair_mac.py::test_a_signed_command_survives_the_transport_retry
G-RPC-26 A failing aiohttp internals probe or loopback self-test (unsolicited bytes on an idle connection) warns once, increments sandloom_rpc_transport_fallback_total and rebuilds the shared client on httpx, which serves calls; the pyproject pin equals VERIFIED_AIOHTTP_RANGE. sandloom/core/tests/test_rpc_transport_compat.py::test_a_failing_probe_warns_once_counts_and_falls_back_to_httpx
sandloom/core/tests/test_rpc_transport_compat.py::test_startup_probe_failure_rebuilds_the_shared_client_on_httpx
sandloom/core/tests/test_rpc_transport_compat.py::test_a_guard_that_no_longer_engages_is_caught_by_the_self_test
sandloom/core/tests/test_rpc_transport_compat.py::test_pyproject_pins_exactly_the_verified_range
G-RPC-27 A pooled connection returns to the pool only after a complete keep-alive response: a timeout, cancellation, parse error or unsolicited byte closes it, so a half-read or stray response never reaches the next call. sandloom/core/tests/test_rpc_transports.py::test_unsolicited_bytes_never_reach_the_next_call
sandloom/core/tests/test_rpc_transports.py::test_cancellation_mid_call_discards_the_connection
sandloom/core/tests/test_rpc_transports.py::test_slow_peer_times_out_and_discards_the_connection
sandloom/core/tests/test_rpc_response_binding.py::test_cancellation_mid_read_never_leaks_the_late_answer
G-RPC-28 No downgrade: an Ed25519-only request is 401 rpc_auth_required, a request with only X-Internal-Token or no credential gets the hidden 404 (INTERNAL_ENDPOINTS_OPEN_IN_DEV never opens /_sandloom/rpc), a bad MAC never falls back to a valid signature riding along, and a signed command stripped of its MAC is refused. sandloom/core/tests/test_rpc_pair_mac.py::test_downgrades_are_refused
sandloom/core/tests/test_service_rpc.py::test_rpc_server_hides_itself_from_a_request_without_credentials
sandloom/core/tests/test_contract_parity.py::test_open_dev_mode_does_not_open_rpc
sandloom/core/tests/test_rpc_pair_mac.py::test_r2_a_signed_command_cannot_be_stripped_of_its_mac
G-RPC-29 The removed settings RPC_AUTH_MODE and RPC_RESPONSE_BINDING (like every config_tiers.REMOVED entry) fail startup unless set to their inert value (service_mac / strict), so no token or unkeyed mode can be re-enabled. sandloom/core/tests/test_config_tiers.py::test_removed_setting_fails_unless_inert
G-RPC-30 Key rotation is per service and fails no call at any step: a provider accepts its current kid and RPC_DH_EXTRA_PRIVATE_KEYS kids (an unpublished extra kid is accepted but flagged), a dropped kid is refused, caller rotation needs no provider change, and the addressed kid is RPC_SERVICE_DH_CURRENT_KIDS or deterministic regardless of JSON order (an unpublished current kid is refused). sandloom/core/tests/test_rpc_pair_mac.py::test_provider_rotation_has_no_failed_call_at_any_step
sandloom/core/tests/test_rpc_pair_mac.py::test_a_request_to_a_dropped_provider_kid_is_refused
sandloom/core/tests/test_rpc_keyed_binding.py::test_caller_key_rotation
sandloom/core/tests/test_security_review_regressions.py::test_pair_mac_key_selection_does_not_depend_on_json_order
G-RPC-31 RPC rejections are logged with ids and kids only (never a key, tag or derived secret), rate-limited per reason and caller, and use a fixed, unique, declared reason vocabulary. sandloom/core/tests/test_rpc_pair_mac.py::test_rejections_log_ids_only_never_key_material
sandloom/core/tests/test_rpc_pair_mac.py::test_r6_rejection_logs_are_rate_limited
sandloom/core/tests/test_rpc_binding_reasons.py::test_every_reason_verify_emits_is_declared
G-RPC-32 HEAD on the status route /_sandloom/rpc/ is 405 (never served under a GET MAC). sandloom/core/tests/test_rpc_pair_mac.py::test_head_on_the_status_route_is_not_served
G-RPC-33 Describe and readiness probes are pair-MAC authenticated like calls and allowed when any method of the contract is allowed for the caller. sandloom/core/tests/test_rpc_allowlist.py::test_proxy_of_an_allowed_caller_calls_and_describes
sandloom/core/tests/test_split_readiness.py::test_required_probes_run_concurrently_capped_and_authenticated

G-MODULE

Id Statement Enforced by
G-MODULE-01 A business module's production code never imports another business module (static, relative, dynamic importlib/sys.modules/getattr spellings, in the framework tree and in an app's modules package); a violation fails sandloom check and the architecture suite. tests/architecture/test_module_boundaries.py::test_module_has_no_forbidden_imports
tests/architecture/test_module_boundaries.py::test_analyzer_detects_violations_in_fixture_modules
tests/architecture/test_archcheck_library.py::test_app_namespace_cross_module_spellings
rule module-boundaries
G-MODULE-02 A business module may import from the kernel ONLY the public_api whitelist (sandloom.core.di, facade, module_api, descriptors, orm, projections) plus sandloom.shared. and its own package; any other sandloom.core. import (settings, drivers, _internal, …) is a framework-imports violation. tests/architecture/test_archcheck_library.py::test_framework_imports_catch_from_sandloom_import_and_dynamic_aliases
tests/architecture/test_archcheck_library.py::test_framework_manifest_passes_every_rule
sandloom/core/tests/test_architecture.py::test_architecture_checker_runs_without_violations
rule framework-imports
G-MODULE-03 The underscore submodules of the public packages (sandloom.core.module_api._, sandloom.core.facade._) are forbidden to business modules via architecture_rules.yml forbidden_patterns; modules import the package itself. tests/architecture/test_archcheck_library.py::test_framework_manifest_passes_every_rule
rule framework-imports
gap (S): no test proves a module importing sandloom.core.module_api._service_registry or sandloom.core.facade._state is flagged (only that the tree is clean)
G-MODULE-04 Module code cannot reach kernel handles: .event_bus, .infra, .service_registry, request.app.state.core (incl. reflective getattr spellings) are rejected; only the public verbs (publish_event, register_event_consumer, cache(), capability(), get_service()) are allowed. tests/architecture/test_no_kernel_handle_access.py::test_module_has_no_kernel_handle_access
tests/architecture/test_no_kernel_handle_access.py::test_analyzer_detects_kernel_handles
tests/architecture/test_no_kernel_handle_access.py::test_analyzer_detects_reflective_kernel_handles
tests/architecture/test_archcheck_library.py::test_kernel_handles_rule_runs_in_sandloom_check
rule kernel-handles
G-MODULE-05 Business modules never call a capability's raw_client() / raw_backend() escape hatch (direct or reflective); a call fails sandloom check. tests/architecture/test_no_raw_provider_usage.py::test_module_has_no_raw_provider_usage
tests/architecture/test_no_raw_provider_usage.py::test_analyzer_detects_raw_call
tests/architecture/test_no_raw_provider_usage.py::test_analyzer_detects_reflective_raw_access
rule raw-provider
G-MODULE-06 Business modules never import a backend SDK (boto3, motor, pymongo, neo4j, temporalio, kafka), the legacy globals (get_db/get_event_bus from sandloom.core, get_cache from sandloom.core.di) or the infra-internal modules (sandloom.core._internal, eventing.event_bus, storage, drivers, sql_alchemy.models). tests/architecture/test_no_legacy_infra_imports.py::test_module_has_no_legacy_infra_imports
tests/architecture/test_no_legacy_infra_imports.py::test_analyzer_detects_legacy_infra_import
rule infra-imports
G-MODULE-07 Business-module code never calls db.commit() / db.rollback(); the Unit of Work owns the transaction boundary and a call fails sandloom check. tests/architecture/test_no_uow_boundary_breaks.py::test_module_does_not_break_uow_boundary
tests/architecture/test_no_uow_boundary_breaks.py::test_analyzer_detects_boundary_break
rule uow-boundary
G-MODULE-08 A best-effort downgrade in module code (publish_event/record_audit with required=False, incl. aliased calls and **kwargs splats) needs a '# sandloom: best-effort ' marker on the call, else sandloom check fails; guaranteed calls are never flagged. tests/architecture/test_best_effort_effects_rule.py::test_fixture_violations_are_flagged_and_marked_calls_pass
tests/architecture/test_best_effort_effects_rule.py::test_aliased_effects_and_kwargs_splat_are_flagged
tests/architecture/test_best_effort_effects_rule.py::test_guaranteed_calls_are_never_flagged
tests/architecture/test_best_effort_effects_rule.py::test_rule_runs_in_sandloom_check
rule best-effort-effects
G-MODULE-09 get_core() is async: an unawaited direct call in module code is flagged by sandloom check, while awaited calls and Depends(get_core) pass. tests/architecture/test_get_core_await_rule.py::test_unawaited_direct_calls_are_flagged
tests/architecture/test_get_core_await_rule.py::test_awaited_calls_and_depends_are_fine
tests/architecture/test_get_core_await_rule.py::test_core_from_request_equals_awaited_get_core
rule get-core-await
G-MODULE-10 HTTP endpoints (router decorators) of a business module live only in its own api/ layer, judged relative to the module root (an api/ directory above the module does not exempt it). tests/architecture/test_no_endpoints_outside_api_layer.py::test_no_endpoints_outside_api_layer
tests/architecture/test_no_endpoints_outside_api_layer.py::test_analyzer_flags_endpoint_outside_api_layer
tests/architecture/test_no_endpoints_outside_api_layer.py::test_api_directory_above_the_module_root_does_not_exempt_it
tests/architecture/test_no_endpoints_outside_api_layer.py::test_sandloom_check_judges_api_layer_relative_to_module
rule endpoints-outside-api
G-MODULE-11 API endpoints of a business module do not leak ORM models (no ORM types in endpoint signatures/returns; they return pydantic DTOs), and the legacy ORM_LEAKAGE_BASELINE is empty and may only shrink. tests/architecture/test_api_no_orm_leakage.py::test_api_no_orm_leakage_in_endpoints
tests/architecture/test_api_no_orm_leakage.py::test_analyzer_flags_orm_leakage_in_endpoints
tests/architecture/test_archcheck_library.py::test_orm_leakage_with_app_model_roots
tests/architecture/test_ledger_static_pins.py::test_orm_leakage_baseline_stays_empty
rule orm-leakage
G-MODULE-12 A business module's schemas/ (package or single schemas.py) never import SQLAlchemy or the module's models (absolute, relative or any models spelling). tests/architecture/test_schemas_no_orm_imports.py::test_business_module_schemas_do_not_import_orm
tests/architecture/test_schemas_no_orm_imports.py::test_analyzer_detects_every_models_spelling
tests/architecture/test_schemas_no_orm_imports.py::test_single_file_schemas_module_is_checked
tests/architecture/test_archcheck_library.py::test_relative_model_imports_count_in_schemas
rule schema-orm-imports
G-MODULE-13 Module tests import the framework only through a whitelist (public_api + shared_api + test_api sandloom.core.testing/sandloom.core.contract_testing + own package); any kernel internal or other business module import fails, and every test allowlist entry must still match a real import (stale entries fail). tests/architecture/test_archcheck_library.py::test_test_imports_is_a_whitelist
tests/architecture/test_archcheck_library.py::test_test_imports_whitelist_runs_in_sandloom_check
tests/architecture/test_archcheck_library.py::test_test_imports_rule_honours_the_allowlist
tests/architecture/test_archcheck_library.py::test_legacy_test_import_baseline_has_no_stale_entries
rule test-imports
G-MODULE-14 register = define_module(name, ...) mounts .api's router, registers .events.EVENT_SCHEMAS and returns the descriptor; re-registering an identical schema is a no-op while a breaking schema change fails the load; inference refuses main; a business module named like a conventional submodule (tasks) owns its declarations, and Saga[T](...) records its constructing module. sandloom/core/tests/test_define_module.py::test_conventions_mount_router_and_register_schemas
sandloom/core/tests/test_define_module.py::test_identical_schema_reregistration_is_idempotent_breaking_still_raises
sandloom/core/tests/test_define_module.py::test_inference_refuses_main
tests/sdk/test_sdk_discovery.py::test_plain_routers_keep_todays_rule_and_module_routers_join
tests/sdk/test_sdk_discovery.py::test_a_declaration_outside_the_conventional_submodules_is_refused
tests/sdk/test_sdk_discovery.py::test_a_refused_module_registers_nothing
tests/sdk/test_sdk_discovery.py::test_events_override_skips_producer_schemas
tests/sdk/test_sdk_discovery.py::test_a_module_named_like_a_submodule_owns_its_declarations
tests/sdk/test_sdk_discovery.py::test_a_subscripted_saga_records_its_constructing_module
G-MODULE-15 Every bundled module (and every example app's module) boots ALONE through create_app + lifespan; only the documented ContractBindingError for required consumed contracts may refuse, and any startup/registration coupling or error logged at startup fails the boot. sandloom_identity/tests/test_module_wiring.py::test_module_boots_alone
sandloom_tenancy/tests/test_module_wiring.py::test_module_boots_alone
sandloom_billing/tests/test_module_wiring.py::test_module_boots_alone
sandloom/core/tests/test_module_boots_alone.py::test_required_missing_contract_is_the_explicit_refusal
sandloom/core/tests/test_module_boots_alone.py::test_startup_coupling_fails_the_boot
sandloom/core/tests/test_module_boots_alone.py::test_registration_coupling_fails_the_boot
examples/linkly/tests/test_modules_boot_alone.py::test_module_boots_alone
examples/projecthub/tests/test_modules_boot_alone.py::test_module_boots_alone
examples/helpdesk/tests/test_modules_boot_alone.py::test_module_boots_alone
G-MODULE-16 Module tables never go in the core migration chain (infra-only: outbox/audit/consumer_inbox/contract_command_log); each module's tables live in exactly one module chain and the chains are disjoint. tests/integration/test_migrations_per_chain.py::test_core_chain_owns_only_infra_tables
tests/integration/test_migrations_per_chain.py::test_chains_are_disjoint_and_cover_all_orm_tables
tests/integration/test_migrations_per_chain.py::test_every_chain_has_a_baseline
G-MODULE-17 Modules reference other modules' entities by plain UUID — no cross-module foreign keys. gap (M): convention only: no sandloom check rule or test rejects a ForeignKey from one module's table to another module's table prefix
G-MODULE-18 A generated module (sandloom add module) carries no framework plumbing: conftest absent or <= 3 lines (framework tree: one-line fixtures import), migrations/env.py <= 3 lines (run_module_migrations(file)), init.py <= 8 non-blank lines with a declarative define_module register. sandloom/core/tests/test_module_generation.py::test_add_module_boilerplate_is_minimal
sandloom/core/tests/test_module_generation.py::test_add_module_register_is_declarative
G-MODULE-19 An application derives its module list: sandloom.sdk.app.app_modules(".modules") finds, statically, every subpackage whose init defines register; sandloom new passes it to create_app as the default (SANDLOOM_MODULES selects a subset). sandloom/core/tests/test_project.py::test_minimal_app_loads_only_its_module
sandloom/core/tests/test_project.py::test_with_batteries_orders_modules

G-KERNEL

Id Statement Enforced by
G-KERNEL-01 sandloom.core is layered (tooling > composition > contracts&RPC > auth&HTTP > drivers > messaging > persistence > bus > infra > foundation > config); every runtime import (module-level and function-local, only TYPE_CHECKING excluded) goes to the same or a lower layer, and the contract is exhaustive so a new sandloom.core child fails until placed. tests/architecture/test_kernel_import_contracts.py::test_kernel_import_contracts_are_kept
import contract kernel-layers
G-KERNEL-02 The kernel has no import cycle at any depth (packages and modules, siblings of one layer included, sub-packages like unit_of_work. / facade.), counting function-local imports. tests/architecture/test_kernel_import_contracts.py::test_kernel_import_contracts_are_kept
import contract kernel-acyclic
G-KERNEL-03 Configuration (sandloom.core.config: settings, tiers, accepted risks) is a leaf: it imports no other kernel package. tests/architecture/test_kernel_import_contracts.py::test_kernel_import_contracts_are_kept
import contract kernel-config-leaf
G-KERNEL-04 No import contract has ignore_imports; should one ever appear it must carry a written '; reason:' line (refused otherwise) and import-linter fails an unmatched entry. tests/architecture/test_kernel_import_contracts.py::test_every_ignored_import_has_a_written_reason
tests/architecture/test_ledger_static_pins.py::test_import_contracts_have_no_ignore_imports
import contract kernel-layers
import contract kernel-acyclic
import contract kernel-config-leaf
G-KERNEL-05 Every kernel function-local import (# noqa: PLC0415) states its category '- optional: ' or '- cold: '; a cycle break is not a category and no kernel file is exempt. tests/architecture/test_lint_suppression_ratchet.py::test_kernel_function_local_imports_state_their_category
G-KERNEL-06 Lint-suppression counts (noqa per rule) only go down, config-level ignores of ratcheted rules are pinned, and blind-except / bandit suppressions carry a reason. tests/architecture/test_lint_suppression_ratchet.py::test_noqa_counts_only_go_down
tests/architecture/test_lint_suppression_ratchet.py::test_config_level_ignores_of_ratcheted_rules_are_pinned
tests/architecture/test_lint_suppression_ratchet.py::test_blind_except_and_bandit_suppressions_carry_a_reason
tests/architecture/test_lint_suppression_ratchet.py::test_marker_scanner_reads_comments_only
G-KERNEL-07 The paradigm-agnostic kernel imports no SQLAlchemy at runtime (TYPE_CHECKING exempt); only the allowlisted relational surface (orm, tenant, di, sql_alchemy/, projections/upsert, migrations/, drivers) may, and eventing never imports the relational implementation. tests/architecture/test_relational_paradigm_boundary.py::test_kernel_has_no_runtime_sqlalchemy_import
tests/architecture/test_relational_paradigm_boundary.py::test_allowlist_entries_are_relational
tests/architecture/test_relational_paradigm_boundary.py::test_analyzer_ignores_typing_only_and_flags_runtime
tests/architecture/test_relational_paradigm_boundary.py::test_eventing_never_imports_the_relational_implementation
G-KERNEL-08 Lower kernel modules never reach UP to the aggregators: no un-baselined runtime import of sandloom.core.facade or sandloom.core.di from inside the kernel, and the facade does not import the concrete RPC mechanism (ratchet baselines may only shrink). tests/architecture/test_core_kernel_layering.py::test_no_unbaselined_runtime_facade_import
tests/architecture/test_no_legacy_infra_imports.py::test_kernel_has_no_unbaselined_upward_di_import
tests/architecture/test_core_kernel_layering.py::test_facade_does_not_import_concrete_rpc
G-KERNEL-09 The audited cross-tenant system channel (sandloom.core.drivers.system_db) is unreachable from business modules: it is a forbidden infra module, absent from the public core API, and the database provider exposes no system engine. tests/architecture/test_system_channel_unreachable.py::test_system_channel_modules_are_forbidden_infra_modules
tests/architecture/test_system_channel_unreachable.py::test_system_channel_not_in_public_core_api
tests/architecture/test_system_channel_unreachable.py::test_module_import_of_system_channel_is_flagged
tests/architecture/test_system_channel_unreachable.py::test_provider_does_not_expose_system_engine
rule infra-imports
G-KERNEL-10 sandloom.shared stays module-agnostic: it never imports a business module (absolute or relative), so it cannot become a transitive coupling channel. tests/architecture/test_shared_purity.py::test_shared_does_not_import_business_modules
G-KERNEL-11 The enforced public surface (the public_api modules of architecture_rules.yml, including the sandloom.sdk modules of Public API 1.0, + sandloom.shared.* and the test surfaces) is snapshotted in public_api_baseline.json and a backward-incompatible change fails the build. sandloom/core/tests/test_review_tooling_checks.py::test_api_gate_preserves_generic_type_arguments_and_default_values
tests/architecture/test_sdk_reference.py::test_every_public_module_carries_a_current_reference_block
G-KERNEL-12 The framework carries no deprecated API: its own suite treats DeprecationWarning from sandloom as an error (pyproject filterwarnings 'error::DeprecationWarning:sandloom'), and generated apps set error::DeprecationWarning. sandloom/core/tests/test_project.py::test_generated_app_fails_on_deprecation_warnings
sandloom/core/tests/test_outbox_relay_backoff.py::test_start_relay_background_never_uses_deprecated_get_event_loop
tests/architecture/test_ledger_static_pins.py::test_framework_deprecations_fail_its_suite
G-KERNEL-13 The kernel names no business module: sandloom.core settings carry no default module list, the standard root sandloom.main composes whatever module packages are installed (their sandloom.modules entry points), and the kernel factory alone builds a working app. sandloom/core/tests/test_generated_project_packaging.py::test_minimal_app_uses_the_kernel_factory
sandloom/core/tests/test_module_names.py::test_the_kernel_names_no_module

G-INFRA

Id Statement Enforced by
G-INFRA-01 A backend constructor (create_async_engine, redis.from_url, Celery(...), Storage(...)) appears only inside sandloom.core.drivers/**; concrete drivers are imported only by the composition layer (app_factory, _internal.db_provider), and the public DI surface / ops health imports no concrete driver. tests/architecture/test_infra_constructed_only_in_drivers.py::test_no_backend_construction_outside_drivers
tests/architecture/test_infra_constructed_only_in_drivers.py::test_analyzer_detects_construction
tests/architecture/test_core_kernel_layering.py::test_concrete_drivers_imported_only_in_composition_layer
tests/architecture/test_no_legacy_infra_imports.py::test_di_public_surface_imports_no_concrete_driver
tests/architecture/test_ops_health_uses_infra_registry.py::test_ops_health_does_not_use_driver_default_accessors
G-INFRA-02 There is no implicit default registry: cache() / notifier() / any capability accessor with no registry bound raises RuntimeError in every environment, with a hint naming create_app, load_composition_core and the sandloom.core.testing harness. sandloom/core/tests/test_di_get_db.py::test_get_db_surfaces_missing_registry
sandloom/core/tests/test_ledger_runtime_pins.py::test_capability_surfaces_raise_without_a_bound_registry
G-INFRA-03 A driver returning a provider that does not implement its capability's contract class is refused with ProviderContractError (also for an explicit contract mismatch at resolve time). sandloom/core/tests/test_capabilities.py::test_wrong_provider_contract_raises
sandloom/core/tests/test_capabilities.py::test_resolve_explicit_contract_mismatch_raises
G-INFRA-04 require_capability(..., features={...}) raises FeatureUnsupportedError when the configured driver does not advertise a requested feature, and an unknown feature name is refused. sandloom/core/tests/test_capabilities.py::test_resolve_unsupported_feature_raises
sandloom/core/tests/test_capabilities.py::test_resolve_unknown_feature_raises
sandloom/core/tests/test_capabilities.py::test_facade_capability_and_require_capability
G-INFRA-05 Resolving an experimental capability (document_store, graph_store, workflow, search, lock, secrets) without a registered driver raises ExperimentalCapabilityError (also via the facade), with the precise error kept for an unknown instance. sandloom/core/tests/test_extra_capabilities.py::test_experimental_without_driver_raises_experimental_error
sandloom/core/tests/test_extra_capabilities.py::test_experimental_configured_with_unregistered_driver_raises
sandloom/core/tests/test_extra_capabilities.py::test_facade_require_capability_on_experimental_raises
sandloom/core/tests/test_extra_capabilities.py::test_experimental_with_driver_but_unknown_instance_keeps_precise_error
G-INFRA-06 The loader preflights a module's CapabilityRequirement list at load without I/O: a required requirement that is unknown / unconfigured / missing a feature fails the module load, an optional one only warns. sandloom/core/tests/test_module_capabilities.py::test_preflight_fails_for_unsupported_feature
sandloom/core/tests/test_module_capabilities.py::test_preflight_unknown_capability_raises
sandloom/core/tests/test_module_capabilities.py::test_optional_requirement_does_not_raise
sandloom/core/tests/test_module_capabilities.py::test_preflight_passes_for_satisfiable_requirement
gap (S): no test asserts the preflight performs no I/O (opens no provider/connection), nor that a failed required preflight aborts create_app for a required module
G-INFRA-07 Infra configuration is validated at registry build: an unknown driver, an unknown instance, a new INFRA instance without a driver, or options failing the capability's config schema are refused with a clear error. sandloom/core/tests/test_infra_registry.py::test_unknown_driver_raises
sandloom/core/tests/test_infra_registry.py::test_unknown_instance_raises
sandloom/core/tests/test_capabilities.py::test_bad_options_raise_config_schema_error
tests/integration/test_resilience_config.py::test_build_infra_registry_rejects_unknown_infra_option
G-INFRA-08 The Redis cache driver's clients follow the running event loop (rebuilt on a new loop, default and named instances alike; an injected client is never replaced), so no harness resets driver singletons. sandloom/core/tests/test_redis_cache_loop_affinity.py::test_default_client_is_rebuilt_on_a_new_loop
sandloom/core/tests/test_redis_cache_loop_affinity.py::test_named_instance_client_follows_the_loop
sandloom/core/tests/test_redis_cache_loop_affinity.py::test_injected_default_client_is_never_replaced

G-CONFIG

Id Statement Enforced by
G-CONFIG-01 Every Settings field is classified in exactly one configuration tier (semantic or ops), and every semantic setting defines a value for every profile (dev / test / prod / prod+multi_tenant_strict). sandloom/core/tests/test_config_tiers.py::test_every_setting_is_in_exactly_one_tier
sandloom/core/tests/test_config_tiers.py::test_every_semantic_setting_defines_every_profile
sandloom/core/tests/test_config_tiers.py::test_profile_table_is_the_current_default_behaviour
G-CONFIG-02 The semantic tier never exceeds 16 settings, a setting that leaves it cannot silently return (baseline only shrinks), and a new semantic setting outside the baseline must reference an ADR. sandloom/core/tests/test_config_tiers.py::test_semantic_tier_is_ratcheted
G-CONFIG-03 A removed setting (config_tiers.REMOVED, incl. the former ALLOW booleans, RPC_AUTH_MODE, RPC_RESPONSE_BINDING and AUTH_REVOCATION_STRICT_PATH_PREFIXES with inert value []) set to its inert value only logs a warning; any other value fails Settings construction / startup. sandloom/core/tests/test_config_tiers.py::test_removed_setting_fails_unless_inert
sandloom/core/tests/test_revocation_freshness.py::test_the_freshness_knobs_are_gone
tests/architecture/test_operation_classes.py::test_strict_path_prefix_satisfies_strictness
G-CONFIG-04 In prod, JWT_SECRET (and JWT_PREVIOUS_SECRETS), INTERNAL_API_TOKEN and METRICS_TOKEN shorter than 32 chars or looking like placeholders are rejected at Settings validation, and METRICS_TOKEN must differ from INTERNAL_API_TOKEN; dev keeps short values working. sandloom/core/tests/test_ops_security_hardening.py::test_prod_rejects_weak_or_placeholder_jwt_secret
sandloom/core/tests/test_ops_security_hardening.py::test_prod_validates_internal_and_metrics_tokens
sandloom/core/tests/test_auth_rotation_revocation.py::test_prod_previous_secrets_follow_jwt_secret_rules
G-CONFIG-05 A semantic setting weaker than its profile value is reported as an override (WARNING at start in prod, INFO for a dev opt-in), listed in /diagnostics/core configuration block and by sandloom doctor; a stronger choice is not reported. sandloom/core/tests/test_config_tiers.py::test_prod_override_is_reported_loud_and_explicit
sandloom/core/tests/test_config_tiers.py::test_stronger_choice_is_not_an_override
sandloom/core/tests/test_config_tiers.py::test_dev_opt_in_is_logged_at_info
sandloom/core/tests/test_config_tiers.py::test_doctor_lists_overrides
gap (S): sandloom_config_override_active{setting,kind} metric export (app_factory) is not asserted by any test.
G-CONFIG-06 Framework runtime code adds no raw environment read (os.getenv / os.environ) beyond the pinned per-file baseline (the composition root, gunicorn config and bootstrap switches); every new knob is a validated Settings field. tests/architecture/test_ledger_static_pins.py::test_raw_environment_reads_only_go_down
G-CONFIG-07 The boot-time configuration checks (enforce_configuration) refuse an invalid configuration identically in every entry point: the app, the Celery worker, sandloom migrate and the sandloom CLI. sandloom/core/tests/test_boot_configuration_checks.py::test_moved_validator_refuses_every_entry_point
G-CONFIG-08 The generated configuration-tiers reference renders every semantic setting (with its reason) and every ops setting, with the per-profile value table. sandloom/core/tests/test_config_tiers.py::test_generated_reference_lists_every_tier
gap (S): AGENTS.md and accepted_risks error messages point at docs/reference/configuration-tiers.md, but that file does not exist in the repo (only tiers.render_markdown() in memory); nothing checks the committed page exists / is current.

G-RISK

Id Statement Enforced by
G-RISK-01 SANDLOOM_ACCEPTED_RISKS accepts only registered risk ids; an unknown id fails Settings validation / startup. sandloom/core/tests/test_config_tiers.py::test_unknown_risk_id_fails_startup
G-RISK-02 Each accepted risk in effect in prod is logged as a WARNING 'ACCEPTED RISK' at every start, reported as an override of kind accepted_risk, listed in /diagnostics/core configuration.accepted_risks and flagged WARN by sandloom doctor; a prod-only risk accepted outside prod is listed with in_effect=false and is not an override. sandloom/core/tests/test_config_tiers.py::test_accepted_risk_is_logged_loud_counted_and_listed
gap (S): Export of sandloom_accepted_risk_active{risk}=1 (app_factory) is never asserted; a test reading the metric after create_app in prod with a risk accepted is missing.
G-RISK-03 Every registered risk documents what it weakens and which check consults it, and the registry holds exactly the nine ids fake_psp_in_prod, log_notifier_in_prod, local_storage_in_prod, volatile_event_bus, unversioned_ddl_repair, weak_password_hash, shared_hs_secret_split, relay_disabled, command_dedupe_below_retry_horizon. sandloom/core/tests/test_config_tiers.py::test_every_risk_is_documented
sandloom/core/tests/test_ledger_runtime_pins.py::test_accepted_risk_ids_are_pinned
G-RISK-04 In prod the volatile event-bus drivers (memory, redis Pub/Sub) are refused unless volatile_event_bus is accepted; required/durable consumers on a Pub/Sub bus are refused even with the risk, and outside prod need the risk. sandloom/core/tests/test_volatile_bus_guards.py::test_driver_refuses_volatile_bus_in_prod
sandloom/core/tests/test_volatile_bus_guards.py::test_memory_bus_allowed_in_dev_refused_in_prod
sandloom/core/tests/test_accepted_risks.py::test_volatile_bus_driver_admitted_in_prod_by_the_risk
sandloom/core/tests/test_accepted_risks.py::test_volatile_consumers_outside_prod_need_the_risk
G-RISK-05 OUTBOX_RELAY_ENABLED=off in a production app process is refused at startup (app_factory.outbox_relay_problem) unless relay_disabled is accepted, and is then reported as an override. sandloom/core/tests/test_accepted_risks.py::test_relay_off_in_prod_is_refused_unless_accepted
sandloom/core/tests/test_accepted_risks.py::test_relay_running_in_prod_is_fine
sandloom/core/tests/test_config_tiers.py::test_relay_off_in_prod_is_an_override_unless_the_risk_is_accepted
G-RISK-06 In prod the log notifier advertises no delivery channel features, so a CapabilityRequirement('notification', features={'email'}) fails, unless log_notifier_in_prod is accepted. sandloom/core/tests/test_notifications.py::test_log_driver_advertises_no_delivery_features_in_prod
sandloom/core/tests/test_notifications.py::test_log_driver_delivery_features_in_prod_are_an_explicit_opt_in
sandloom/core/tests/test_accepted_risks.py::test_log_notifier_channels_in_prod_need_the_risk
G-RISK-07 BILLING_PROVIDER=fake is refused in prod by a critical startup task unless fake_psp_in_prod is accepted. sandloom_billing/tests/test_webhook_hardening.py::TestForgedWebhooks::test_prod_refuses_fake_psp
sandloom_billing/tests/test_webhook_hardening.py::TestForgedWebhooks::test_register_adds_critical_startup_guard_in_prod
sandloom_billing/tests/test_registration_surface.py::test_fake_psp_in_prod_adds_a_critical_startup_task
G-RISK-08 The local filesystem object store refuses to build in prod unless local_storage_in_prod is accepted. sandloom/core/tests/test_infra_review_20261022.py::test_production_local_storage_requires_explicit_shared_volume_opt_in
G-RISK-09 Prod refuses argon2 password-hash parameters below the OWASP argon2id minimum (with a fix naming SANDLOOM_ACCEPTED_RISKS=["weak_password_hash"]) unless weak_password_hash is accepted; outside prod weak parameters only warn. sandloom/core/tests/test_password_hash_config.py::test_prod_refuses_weak_parameters_with_a_fix
sandloom/core/tests/test_password_hash_config.py::test_prod_accepts_presets_and_the_explicit_opt_out
sandloom/core/tests/test_password_hash_config.py::test_weak_parameters_are_allowed_outside_prod_with_a_warning
G-RISK-10 A split prod process using a shared HS JWT secret is refused unless shared_hs_secret_split is accepted, and the multi_tenant_strict profile refuses that risk outright. sandloom/core/tests/test_accepted_risks.py::test_shared_hs_secret_split_in_prod_needs_the_risk
sandloom/core/tests/test_asymmetric_jwt.py::test_prod_split_deployment_refuses_a_shared_hs_secret
sandloom/core/tests/test_accepted_risks.py::test_strict_profile_refuses_the_shared_secret_risk
G-RISK-11 sandloom migrate ensure-outbox --force --apply in prod is refused (exit 1) unless unversioned_ddl_repair is accepted. gap (S): No test exercises the unversioned_ddl_repair gate in sandloom/core/cli/migrate.py (only ensure-outbox dry-run / dev apply are tested).
G-RISK-12 Production startup refuses a command dedupe TTL below the retry horizon (GuaranteeWindowError naming [command_dedupe_below_retry_horizon]) unless that risk is accepted; dev only warns. sandloom/core/tests/test_guarantee_windows.py::test_production_refuses_and_names_the_risk
sandloom/core/tests/test_guarantee_windows.py::test_dev_warns

G-MIGRATE

Id Statement Enforced by
G-MIGRATE-01 Each module owns its own Alembic chain with version table alembic_version_; chains are disjoint, together cover every ORM table, and reproduce the ORM schema exactly. tests/integration/test_migrations_per_chain.py::test_chains_are_disjoint_and_cover_all_orm_tables
tests/integration/test_migrations_per_chain.py::test_chains_reproduce_orm_schema_exactly
tests/integration/test_migrations_per_chain.py::test_every_chain_has_a_baseline
G-MIGRATE-02 A module chain is applied to exactly one database: migrations_database if set, else the single required database requirement, else default; optional requirements never host it, and several required instances without default fail unless migrations_database is set. sandloom/core/tests/test_loader_migrations_hardening.py::test_database_instances_single_target
sandloom/core/tests/test_loader_migrations_hardening.py::test_optional_database_never_hosts_chain
sandloom/core/tests/test_loader_migrations_hardening.py::test_ambiguous_databases_require_explicit_target
G-MIGRATE-03 Two modules resolving to the same migrations_chain key fail the run / module load. sandloom/core/tests/test_loader_migrations_hardening.py::test_chain_collision_detected
sandloom/core/tests/test_loader_migrations_hardening.py::test_loader_rejects_colliding_chain
G-MIGRATE-04 Migration runs hold a per-database advisory lock: a concurrent run waits at most --lock-timeout seconds (must be positive) and logs the holder's pid / application_name. sandloom/core/tests/test_loader_migrations_hardening.py::test_migration_lock_serialises_concurrent_runs
sandloom/core/tests/test_ops_cli.py::test_lock_wait_names_the_holder_and_honours_the_timeout
sandloom/core/tests/test_ops_cli.py::test_lock_timeout_must_be_positive
G-MIGRATE-05 The core Alembic chain creates only shared infrastructure tables (outbox_messages, audit_log, consumer_inbox, contract_command_log, http_idempotency_log); module tables never go in it. tests/integration/test_migrations_per_chain.py::test_core_chain_owns_only_infra_tables
gap (S): Doc drift: AGENTS.md lists four core tables (outbox/audit/consumer_inbox/contract_command_log) but the chain (and the test) also own http_idempotency_log.
G-MIGRATE-06 sandloom migrate ensure-outbox is a dry run unless --apply, and ensure-outbox --apply is refused in prod without --force (prod uses upgrade-core). sandloom/core/tests/test_migrations_multidb.py::test_ensure_outbox_dry_run_is_noop
gap (S): The prod refusal of ensure-outbox --apply without --force (cli/migrate.py) is untested.
G-MIGRATE-07 sandloom migrate extract-module is a dry run by default (writes nothing); --apply requires --i-have-stopped-writes, the fence blocks a live writer, and a same-database, non-empty target, a source behind head, or an RLS-enabled target role are refused before copying. sandloom/core/tests/test_module_extraction.py::test_dry_run_writes_nothing
sandloom/core/tests/test_module_extraction.py::test_apply_requires_stopped_writes
sandloom/core/tests/test_module_extraction.py::test_live_writer_blocks_the_fence
sandloom/core/tests/test_module_extraction.py::test_non_empty_target_is_refused
G-MIGRATE-08 extract-module is crash-resumable and verifiable (row counts + checksums vs manifest), --unfence rolls back only before completion (after it needs --force), --drop-source is a separate explicit cleanup, and the consumer group-owner fingerprint is transferred to the target database. sandloom/core/tests/test_module_extraction.py::test_extract_crash_resume_verify_and_cleanup
sandloom/core/tests/test_module_extraction.py::test_unfence_rolls_back_before_completion
sandloom/core/tests/test_module_extraction.py::test_group_owner_transfer_redis_transport
sandloom/core/tests/test_module_extraction.py::test_shared_rows_stream_idempotently_and_detect_missing_keys
G-MIGRATE-09 sandloom migrate resolves the module set through the composition root (SANDLOOM_COMPOSITION_ROOT) and upgrade-all exits non-zero on failure, including a database stamped at a non-head revision. sandloom/core/tests/test_loader_migrations_hardening.py::test_cli_uses_composition_root_module_set
sandloom/core/tests/test_loader_migrations_hardening.py::test_upgrade_all_rejects_non_head_rev
tests/integration/test_migration_revision_cli.py::test_revision_respects_composition_root
G-MIGRATE-10 Module chains are applied in a deterministic order honouring declared dependencies; a dependency cycle or a missing dependency of a required module is fatal. sandloom/core/tests/test_migrations_ordering.py::test_order_is_deterministic_and_respects_dependencies
sandloom/core/tests/test_migrations_ordering.py::test_cycle_is_fatal
sandloom/core/tests/test_migrations_ordering.py::test_missing_dependency_of_required_module_is_fatal
G-MIGRATE-11 Migrations derive an explicit postgresql+psycopg2:// URL from the app's asyncpg URL (to_sync_url, idempotent, mapping asyncpg ssl= to libpq sslmode=). sandloom/core/tests/test_migrations_multidb.py::test_to_sync_url_is_explicit_and_idempotent
sandloom/core/tests/test_migrations_multidb.py::test_to_sync_url_translates_asyncpg_ssl_to_sslmode
G-MIGRATE-12 sandloom migrate drift (and the sandloom test step "migration drift", SANDLOOM-T006) applies the core chain and every module chain, in the upgrade-all dependency order, to a scratch database on the default database's server (created and dropped by the check) and compares each chain with its models (each env.py compares only the module's tables); a difference fails naming the module, the operations found and the fix (sandloom migrate new ""). The configured databases are never written. tests/sdk/test_sdk_generators.py::test_migration_drift_names_the_module_and_the_fix
sandloom/core/tests/test_run_tests_cli.py::test_the_plan_of_the_framework_tree

G-OPS

Id Statement Enforced by
G-OPS-01 Celery beat schedules only while it holds a fenced cache lease: a second beat stands by and takes over on release/death, a holder that lost its token stops at once, a cache outage keeps scheduling only until a local deadline, and beat refuses to start without a CAS-capable cache. sandloom/core/tests/test_beat_lease.py::test_second_beat_stands_by_and_takes_over_after_release
sandloom/core/tests/test_beat_lease.py::test_a_holder_that_lost_its_token_stops_scheduling_at_once
sandloom/core/tests/test_beat_lease.py::test_cache_outage_keeps_scheduling_only_until_the_local_deadline
sandloom/core/tests/test_beat_lease.py::test_beat_refuses_to_start_without_a_cas_cache
G-OPS-02 /health and /ready answer 503 when degraded; public callers see check statuses only (details and /ready topology need X-Internal-Token) and results are cached for HEALTH_CACHE_TTL_SECONDS. sandloom/core/tests/test_ops_security_hardening.py::test_public_health_probes_are_cached_and_topology_hidden
sandloom/core/tests/test_consumer_runtime.py::test_ready_endpoint_rejects_unavailable_required_consumer
sandloom/core/tests/test_module_subset.py::test_subset_whose_only_module_failed_is_not_ready
sandloom/core/tests/test_revocation_never_dead_lettered.py::test_ready_degrades_while_a_revocation_is_stuck_liveness_unaffected
G-OPS-03 Peers never feed /health (liveness); a down peer fails only /ready and only when its SERVICE_BINDINGS entry is required: true, with concurrent, capped, authenticated probes. sandloom/core/tests/test_split_readiness.py::test_down_peer_does_not_fail_health_or_ready_by_default
sandloom/core/tests/test_split_readiness.py::test_required_peer_down_fails_ready_but_never_health
sandloom/core/tests/test_split_readiness.py::test_required_probes_run_concurrently_capped_and_authenticated
G-OPS-04 /metrics, /diagnostics/, /contracts/ and health details are closed without INTERNAL_API_TOKEN (open in dev only with INTERNAL_ENDPOINTS_OPEN_IN_DEV, never in prod); METRICS_TOKEN unlocks /metrics only. sandloom/core/tests/test_internal_auth.py::test_closed_in_dev_without_token_by_default
sandloom/core/tests/test_internal_auth.py::test_dev_opt_in_ignored_in_prod
sandloom/core/tests/test_internal_auth.py::test_metrics_token_unlocks_only_metrics
sandloom/core/tests/test_internal_auth.py::test_disabled_in_prod_without_token
G-OPS-05 Workers are health-checked by python -m sandloom.core.worker healthcheck, which fails when the heartbeat written on the worker timer is stale; every deploy profile's worker uses it and is sized from the CPU quota. sandloom/core/tests/test_worker.py::test_heartbeat_freshness
sandloom/core/tests/test_worker.py::test_healthcheck_cli
sandloom/core/tests/test_worker.py::test_heartbeat_step_runs_on_the_worker_timer
sandloom/core/tests/test_deploy_profiles.py::test_workers_are_sized_and_health_checked
G-OPS-06 Under gunicorn exactly one elected worker runs the outbox relay and the others are delegated; the leader is re-elected after a crash, TTOU retirement or SIGHUP reload, and delegated is refused outside a gunicorn worker. sandloom/core/tests/test_gunicorn_conf.py::test_gunicorn_elects_exactly_one_relay_leader
sandloom/core/tests/test_gunicorn_conf.py::test_crashed_leader_is_replaced_without_recycling_others
sandloom/core/tests/test_gunicorn_conf.py::test_relay_leader_is_reelected_when_ttou_retires_it
sandloom/core/tests/test_accepted_risks.py::test_delegated_relay_needs_a_gunicorn_worker
G-OPS-07 A test run refuses (exit 4, before any test) a test database or Redis index stamped by another owner (SANDLOOM_TEST_OWNER / checkout root) unless SANDLOOM_TEST_TAKE_OWNERSHIP=1; flush_owned_redis never flushes another run's Redis. sandloom/core/tests/test_testing_ownership.py::test_an_unmarked_database_is_claimed_then_refused_to_others
sandloom/core/tests/test_testing_ownership.py::test_a_run_on_a_foreign_database_aborts_before_any_test
sandloom/core/tests/test_testing_ownership.py::test_flush_refuses_another_runs_redis_and_keeps_its_data
sandloom/core/tests/test_testing_ownership.py::test_takeover_is_explicit
G-OPS-08 The harness's db_engine refuses to reset any database whose name lacks 'test', even with ENVIRONMENT=test. sandloom/core/tests/test_testing_plugin.py::test_refuses_a_non_test_database
sandloom/core/tests/test_review_tooling_regressions.py::test_environment_test_does_not_authorize_wiping_production
G-OPS-09 Test processes get small pools and the session fails fast before any test when the Postgres server lacks connection headroom; -n auto is capped to the server. sandloom/core/tests/test_testing_connections.py::test_a_run_without_room_aborts_before_any_test
sandloom/core/tests/test_testing_connections.py::test_auto_workers_are_capped_to_the_server
sandloom/core/tests/test_testing_connections.py::test_a_test_process_needs_a_small_fixed_number_of_connections
G-OPS-10 create_app inside a test does not leak: the harness restores the active core / infra / bus / module lists after every test; every test module starts from the harness app's runtime (built before the first module, whatever ran before); what a test registers on the bound core (event schemas, services, startup tasks, declared operations, ...) is rewound after it, SQLAlchemy's class-level event flags are put back and the RPC client's description caches emptied. The test steps of sandloom test and CI run in a seeded random order (printed, replayable), and CI runs the whole framework suite serially in one shuffled process. sandloom/core/tests/test_testing_isolation.py::test_step_1_a_test_builds_its_own_app
sandloom/core/tests/test_testing_isolation.py::test_step_2_the_next_test_gets_the_previous_runtime_back
sandloom/core/tests/test_testing_isolation.py::test_restore_puts_back_identity_and_content
sandloom/core/tests/test_testing_isolation.py::test_a_registered_channel_is_gone_after_the_test
sandloom/core/tests/test_testing_isolation.py::test_class_flags_and_process_caches_are_put_back
sandloom/core/tests/test_testing_shuffle.py::test_a_seed_fixes_the_order_and_is_printed_for_replay
sandloom/core/tests/test_testing_shuffle.py::test_files_stay_contiguous_and_their_tests_are_shuffled
sandloom/core/tests/test_run_tests_cli.py::test_the_test_steps_run_in_a_seeded_random_order
G-OPS-11 sandloom config dump redacts secrets recursively (incl. URL credentials) and refuses --show-secrets in prod. sandloom/core/tests/test_ops_cli.py::test_redaction_is_recursive_and_masks_url_credentials
sandloom/core/tests/test_ops_cli.py::test_show_secrets_is_refused_in_prod
sandloom/core/tests/test_ops_cli.py::test_secret_names
G-OPS-12 sandloom security revoke-sessions / force-password-reset are dry runs (count targets, call no command) unless --apply --reason, require exactly one target, and in prod also --confirm-production. sandloom/core/tests/test_security_cli.py::test_dry_run_counts_targets_and_calls_no_command
sandloom/core/tests/test_security_cli.py::test_cli_requires_exactly_one_target_and_a_reason_to_apply
gap (S): The prod --confirm-production requirement of sandloom security is untested (only sandloom backup's guard rails test it).
G-OPS-13 Logs (structured extras and free-text messages), exception descriptions and Sentry events are scrubbed of secrets/PII before emission. sandloom/core/tests/test_ops_security_hardening.py::test_log_extras_redacted_by_substring
sandloom/core/tests/test_ops_security_hardening.py::test_free_text_messages_are_scrubbed
sandloom/core/tests/test_ops_security_hardening.py::test_sentry_before_send_scrubs_event
sandloom/core/tests/test_ops_security_hardening.py::test_describe_exception_drops_statement_params
G-OPS-14 In both compose profiles every security/auth-relevant setting (shared env anchor: JWT_* except issuer-only keys, token lifetimes, INTERNAL_API_TOKEN, notifier/SMTP, BILLING_DENY_ON_PAST_DUE) and CACHE_URL are identical in every process. sandloom/core/tests/test_deploy_profiles.py::test_security_relevant_settings_identical_across_processes
sandloom/core/tests/test_deploy_profiles.py::test_parity_keys_default_identically_in_both_profiles
G-OPS-15 The microservices gateway never routes /_sandloom, /diagnostics or /metrics, re-resolves services through Docker DNS, and caps bodies at the app upload limit. sandloom/core/tests/test_deploy_profiles.py::test_gateway_never_exposes_internal_surfaces
sandloom/core/tests/test_deploy_profiles.py::test_gateway_follows_dns_and_fails_fast
sandloom/core/tests/test_deploy_profiles.py::test_gateway_body_limit_matches_app_upload_limit
G-OPS-16 Each app/worker process waits for its one-shot migrate service (sandloom migrate upgrade-all --apply, depends_on service_completed_successfully) against its own database before starting. sandloom/core/tests/test_deploy_profiles.py::test_microservices_db_per_service_and_bindings
tests/architecture/test_ledger_static_pins.py::test_monolith_migrate_one_shot_gates_every_process
G-OPS-17 Every set of periodic tasks in a deploy profile is scheduled by exactly one beat, and modules with periodic tasks are discovered by it. sandloom/core/tests/test_deploy_profiles.py::test_every_periodic_task_set_has_exactly_one_beat
sandloom/core/tests/test_deploy_profiles.py::test_periodic_task_modules_are_discovered
G-OPS-18 sandloom backup verify fails when the restore fails, a chain is behind its head, or orphan rows exist, always dropping its scratch database; destructive operator paths in prod need --confirm-production. sandloom/core/tests/test_backup_cli.py::test_verify_fails_when_a_chain_is_behind_its_head
sandloom/core/tests/test_backup_cli.py::test_verify_fails_when_the_restore_fails
sandloom/core/tests/test_backup_cli.py::test_verify_flags_orphan_rows_of_a_broken_restore
sandloom/core/tests/test_backup_cli.py::test_operator_guard_rails
G-OPS-19 After a security-cache restore from a stale snapshot, sandloom backup reconcile-security re-derives block and revocation marks from durable records so no blocked user is re-enabled and no newer mark in the cache is lowered. sandloom/core/tests/test_security_recovery.py::test_stale_snapshot_after_block_does_not_reenable_the_user
sandloom/core/tests/test_security_recovery.py::test_stale_snapshot_after_unblock_keeps_the_user_blocked_unless_lifted
sandloom/core/tests/test_security_recovery.py::test_lost_revocation_is_reapplied_from_the_outbox
sandloom/core/tests/test_security_recovery.py::test_a_newer_mark_in_the_cache_is_never_lowered
G-OPS-20 The production image (deploy/docker/Dockerfile) builds from the locked uv.lock and runs as a non-root user. tests/architecture/test_ledger_static_pins.py::test_backend_image_is_locked_and_non_root
G-OPS-21 Every module listed explicitly in SANDLOOM_MODULES is required (load failure aborts startup) unless MODULE_REQUIRED names a subset, and every deploy process lists its modules as required. sandloom/core/tests/test_split_readiness.py::test_explicitly_listed_modules_are_required_by_default
sandloom/core/tests/test_split_readiness.py::test_explicit_module_required_wins
sandloom/core/tests/test_deploy_profiles.py::test_listed_modules_are_required_in_every_process
G-OPS-22 No secret file the generators write enters an sandloom new application's image. Its .dockerignore excludes env files (all but .env.example) and the key files of sandloom keys generate --out-dir (jwt_private_key, rpc_private_key, rpc_dh_private_key, *.pem) at any depth, so the split's deploy/.env.microservices (every service's private keys) stays out of the build context; its Dockerfile copies only pyproject.toml, uv.lock and the package into the build stage and nothing of the context into the runtime stage; sandloom add deploy patches an existing app's .dockerignore that lacks the exclusions; the app-split job exports the app's own build context with Docker and finds no secret file, and the smoke builds the service image from that context and finds none in it. sandloom/core/tests/test_image_secrets.py::test_dockerignore_rules_follow_docker
sandloom/core/tests/test_image_secrets.py::test_the_generated_context_ships_no_secret_file
sandloom/core/tests/test_image_secrets.py::test_the_generated_dockerfile_copies_only_what_it_builds
sandloom/core/tests/test_image_secrets.py::test_sandloom_add_deploy_patches_an_old_dockerignore
sandloom/core/tests/test_image_secrets.py::test_docker_agrees_the_context_has_no_secret
sandloom/core/tests/test_scaffold_deploy.py::test_deploy_patches_a_dockerignore_that_would_ship_the_env_file
tests/e2e/test_app_split.py::test_an_sandloom_new_app_splits_into_microservices

G-SAGA

Id Statement Enforced by
G-SAGA-01 register_saga refuses a malformed definition deterministically at registration (duplicate steps, missing compensation, steps on the wrong side of the pivot, more than one pivot, a call that is not a command) with a fix hint. sandloom/core/tests/test_sagas_definition.py::test_structure_is_refused_with_a_fix
sandloom/core/tests/test_sagas_definition.py::test_contract_problems_are_refused
sandloom/core/tests/test_sagas_definition.py::test_parameter_typo_names_the_fix
sandloom/core/tests/test_sagas_definition.py::test_a_remote_provider_is_checked_against_its_snapshot
G-SAGA-02 Registering a saga adds a ContractRequirement for every called method to the module's consumes, so a missing provider fails startup. sandloom/core/tests/test_sagas_definition.py::test_requirements_cover_every_called_method
sandloom/core/tests/test_sagas_definition.py::test_register_saga_buffers_and_the_loader_merges_consumes
G-SAGA-03 Saga instance tables are reached across tenants only through the audited system channel under tenant RLS. On every database, with or without tenant RLS, the runtime itself scopes saga state to the bound tenant (round 3) - saga_status and start_saga's create-or-get read only the bound tenant's instance (no tenant bound - only a tenant-less saga), so another tenant asking for a business id gets nothing (no instance id, status or step) and its start conflicts, and a nudge moves an instance only of the tenant it names; only the system channel (sweep, operator commands) and the test harness read across tenants (saga_store.ANY_TENANT, a required argument of every instance read). The bound tenant is read once the unit is open (round 4) - saga_status outside a unit opens its own, which binds SINGLE_TENANT_ID under TENANCY_MODE=single, so a canonical-tenant saga is found, not reported absent. sandloom/core/tests/test_sagas_channel.py::test_rls_databases_go_through_the_system_channel
sandloom/core/tests/test_sagas_channel.py::test_problem_only_for_rls_without_a_system_channel
sandloom/core/tests/test_sagas_ops.py::test_saga_state_never_crosses_tenants_without_rls
sandloom/core/tests/test_sagas_ops.py::test_single_tenant_status_and_start_see_the_canonical_tenants_saga
G-SAGA-04 Every command step carries the derived operation key saga::::do|undo; non-deterministic arguments hit the command log's conflict and park the instance. sandloom/core/tests/test_sagas_definition.py::test_operation_key_rule
sandloom/core/tests/test_sagas_protocol.py::test_happy_path_runs_every_step_once_with_derived_keys
sandloom/core/tests/test_sagas_recovery.py::test_nondeterministic_args_hit_the_command_log_conflict_and_park
G-SAGA-05 start_saga runs in the caller's Unit of Work (instance row and first nudge commit with the business change), is create-or-get on saga_id, conflicts on different input, and is refused outside a UoW. sandloom/core/tests/test_sagas_protocol.py::test_start_publishes_the_first_nudge_in_the_callers_transaction
sandloom/core/tests/test_sagas_ops.py::test_start_is_create_or_get_and_conflicts_on_other_input
sandloom/core/tests/test_sagas_ops.py::test_start_outside_a_unit_of_work_is_refused
G-SAGA-06 Duplicate, stale or concurrent nudges never run a step twice: the row lock and version check make them no-ops, and a locked instance is skipped, not waited for. sandloom/core/tests/test_sagas_protocol.py::test_duplicate_deliveries_never_run_a_step_twice
sandloom/core/tests/test_sagas_protocol.py::test_committed_transition_redelivered_is_a_no_op
sandloom/core/tests/test_sagas_protocol.py::test_a_locked_instance_is_skipped_not_waited_for
sandloom/core/tests/test_sagas_protocol.py::test_malformed_nudge_is_poison
tests/laws/test_saga_laws.py::test_saga_laws_hold_outside_the_crash_then_deadline_gap
tests/laws/test_saga_laws.py::test_steps_are_idempotent_and_status_only_moves_forward
tests/laws/test_saga_laws.py::test_a_deadline_after_a_crash_resolves_the_step_before_compensating
G-SAGA-07 A crash before the step call or after the provider committed converges without re-applying the step (same key, command-log replay). sandloom/core/tests/test_sagas_protocol.py::test_crash_before_the_call_rolls_back_and_the_redrive_runs_the_step
sandloom/core/tests/test_sagas_protocol.py::test_crash_after_the_provider_committed_replays_the_same_key
sandloom/core/tests/test_sagas_protocol.py::test_provider_in_flight_is_transient_and_retried
tests/laws/test_saga_laws.py::test_a_deadline_after_a_crash_resolves_the_step_before_compensating
G-SAGA-08 The timer sweep drives due backoffs and deadlines at most once per interval and purges terminal instances after retention. sandloom/core/tests/test_sagas_ops.py::test_sweep_nudges_due_instances_once_per_interval
sandloom/core/tests/test_sagas_ops.py::test_sweep_drives_a_deadline_even_while_backing_off
sandloom/core/tests/test_sagas_ops.py::test_sweep_purges_terminal_instances_after_retention
sandloom/core/tests/test_sagas_ops.py::test_sweep_leaves_a_fresh_nudge_alone_until_an_interval_passed
G-SAGA-09 Step outcomes are classified by an explicit closed table (408/423/425/429/5xx transient; domain 4xx refused; key conflict and misconfiguration park); an unknown status is transient. sandloom/core/tests/test_sagas_definition.py::test_outcome_table
sandloom/core/tests/test_sagas_definition.py::test_exceptions_classify_through_the_table
sandloom/core/tests/test_sagas_recovery.py::test_401_parks_as_misconfigured
sandloom/core/tests/test_sagas_recovery.py::test_argument_binding_error_parks_as_misconfigured
G-SAGA-10 Recovery: a refusal before the pivot compensates applied steps in reverse; after the pivot, or of a compensation, it parks with an audit row; transient outcomes retry with full-jitter backoff until the deadline, then compensate or park. sandloom/core/tests/test_sagas_recovery.py::test_refused_before_the_pivot_compensates_the_applied_steps
sandloom/core/tests/test_sagas_recovery.py::test_refused_first_step_compensates_nothing
sandloom/core/tests/test_sagas_recovery.py::test_refused_after_the_pivot_parks_with_an_audit_row
sandloom/core/tests/test_sagas_recovery.py::test_refused_compensation_parks
sandloom/core/tests/test_sagas_recovery.py::test_transient_before_the_pivot_retries_with_full_jitter_backoff
sandloom/core/tests/test_sagas_recovery.py::test_transient_until_the_deadline_before_the_pivot_compensates
sandloom/core/tests/test_sagas_recovery.py::test_transient_after_the_pivot_parks_at_the_deadline
sandloom/core/tests/test_sagas_recovery.py::test_transient_compensation_parks_at_the_compensation_deadline
sandloom/core/tests/test_sagas_recovery.py::test_deadline_before_the_pivot_starts_compensation
sandloom/core/tests/test_sagas_recovery.py::test_deadline_after_the_pivot_parks
sandloom/core/tests/test_sagas_recovery.py::test_compensation_deadline_parks_a_backward_instance
G-SAGA-11 A step with an ambiguous outcome is resolved (re-issued with the same key) before it is compensated; a timeout racing a completing step is serialized by the row lock; also after a crash that rolled back the attempt count: a command step's first forward call writes a durable started marker in its own transaction, and a deadline resolves a step that may have applied (attempt > 0 or the marker) before compensating. sandloom/core/tests/test_sagas_recovery.py::test_ambiguous_step_is_resolved_before_it_is_compensated
sandloom/core/tests/test_sagas_recovery.py::test_resolve_that_finds_a_refusal_compensates_nothing
sandloom/core/tests/test_sagas_recovery.py::test_resolved_pivot_that_applied_never_compensates
sandloom/core/tests/test_sagas_recovery.py::test_timeout_racing_a_completing_step_is_serialized_by_the_row_lock
tests/laws/test_saga_laws.py::test_a_deadline_after_a_crash_resolves_the_step_before_compensating
tests/laws/test_saga_laws.py::test_steps_are_idempotent_and_status_only_moves_forward
G-SAGA-12 No blind re-call after the command-dedupe TTL: an operator retry of a step with an unknown outcome is refused (resolve first), and the runtime parks such a step with outcome_unknown_after_ttl; a call whose transition rolled back after the provider committed (only its durable started marker survives) is recovered as a transient attempt from the marker's own time, so recovery after the TTL parks it instead of re-issuing the command. sandloom/core/tests/test_sagas_ops.py::test_retry_refuses_an_unknown_outcome_after_the_dedupe_ttl
sandloom/core/tests/test_sagas_ops.py::test_retry_after_the_ttl_proceeds_when_the_outcome_is_known
sandloom/core/tests/test_sagas_recovery.py::test_ttl_guard_parks_an_ambiguous_step_whose_record_may_be_gone
sandloom/core/tests/test_sagas_cli.py::test_post_ttl_retry_exits_1_with_the_resolve_hint
sandloom/core/tests/test_sagas_recovery.py::test_a_crash_after_the_provider_committed_is_never_reissued_after_the_ttl
G-SAGA-13 Production refuses a saga whose retry and compensation horizon exceeds the command-dedupe TTL unless the risk is accepted. sandloom/core/tests/test_sagas_definition.py::test_saga_horizon_fits_the_dedupe_ttl
sandloom/core/tests/test_sagas_definition.py::test_saga_horizon_refuses_in_prod_unless_accepted
G-SAGA-14 An instance of an unregistered definition version parks with unknown_definition instead of crashing the consumer. sandloom/core/tests/test_sagas_protocol.py::test_unknown_definition_parks_instead_of_crashing
sandloom/core/tests/test_sagas_cli.py::test_doctor_warns_about_parked_and_unknown_versions
G-SAGA-15 Operator mutations (retry, park, resolve) are dry runs unless --apply and are audited. sandloom/core/tests/test_sagas_cli.py::test_mutations_are_dry_runs_without_apply
sandloom/core/tests/test_sagas_ops.py::test_park_then_retry_resumes_with_audit_rows
sandloom/core/tests/test_sagas_ops.py::test_resolve_requires_a_parked_instance
sandloom/core/tests/test_sagas_ops.py::test_resolve_skipped_compensation_moves_backward

G-SDK

Id Statement Enforced by
G-SDK-01 Importing a public sandloom.sdk module, sandloom.sdk.testing or a bundled module (identity, tenancy, billing) is side-effect free: with python -I, an empty environment and no .env it reads no environment variable, constructs no Settings, opens no socket or non-package file, installs no logging, signal or atexit handler, starts no thread and builds no engine or client; help(sandloom.sdk.events) works. Settings are lazy and every entry point forces them (enforce_configuration / load_settings), so an invalid environment still fails at startup. tests/architecture/test_sdk_import_isolation.py::test_sdk_import_is_side_effect_free
tests/architecture/test_sdk_import_isolation.py::test_the_audit_sees_an_entry_point_forcing_settings
sandloom/core/tests/test_lazy_settings.py::test_import_does_not_validate_and_first_use_does
sandloom/core/tests/test_lazy_settings.py::test_an_invalid_environment_still_fails_at_the_entry_point
sandloom/core/tests/test_lazy_settings.py::test_loaded_settings_are_a_plain_settings_instance
G-SDK-02 sandloom.sdk imports only the public kernel surface (no kernel internal, no private submodule of a public kernel package); the kernel (sandloom.core, sandloom.shared) never imports sandloom.sdk; L1 (sandloom.sdk._decl) imports no kernel module; and sandloom.sdk is layered public modules > _decl > _prim. tests/architecture/test_kernel_import_contracts.py::test_kernel_import_contracts_are_kept
import contract sdk-kernel-surface
import contract kernel-never-imports-sdk
import contract sdk-decl-l1
import contract sdk-layers
G-SDK-03 L1 registration equals L2: for a fixture module per L1 feature (router, producer events, views and consumers, a contract provider, a saga with a typed step, a projection with full-state, tombstone and delta groups) and for every example module, the descriptor and the recorded registration calls built by sandloom.sdk define_module equal those of a hand-written L2 register; the module_router routes carry exactly the per-route dependencies of the L2 form. tests/sdk/test_sdk_l1_registration.py::test_l1_equals_l2_registration
tests/sdk/test_sdk_l1_registration.py::test_module_router_routes_equal_the_l2_router
tests/sdk/test_sdk_l1_registration.py::test_example_modules_register_identically_through_l1
G-SDK-04 Generated code (sandloom new, every sandloom add generator) and every example file that imports sandloom.sdk use only the 29 beginner names; an advanced name needs # sandloom: advanced <reason> on its import (SANDLOOM-T009). tests/sdk/test_sdk_generators.py::test_the_beginner_tier_is_the_29_names
tests/sdk/test_sdk_generators.py::test_generated_code_uses_only_the_beginner_tier
tests/sdk/test_sdk_generators.py::test_examples_importing_the_sdk_stay_in_the_beginner_tier
tests/sdk/test_sdk_generators.py::test_the_pin_reports_an_unmarked_advanced_name
G-SDK-05 Every name sandloom.sdk._decl (L1) imports from sandloom.sdk._prim is exported by a public sandloom.sdk module, and every _prim name is public: each L1 dependency is an advertised L2 primitive; the H1 names (public, at_commit, db, open_uow, sensitive_effect, MoneyLedger, Account, authorizes, ...) are the kernel objects themselves, no wrapper. tests/architecture/test_public_api_sdk.py::test_l1_uses_only_public_l2
tests/architecture/test_public_api_sdk.py::test_h1_names_are_the_kernels
tests/architecture/test_public_api_sdk.py::test_db_and_open_uow_are_the_kernel_units
tests/architecture/test_public_api_sdk.py::test_public_is_the_kernel_marker

G-DECL

Id Statement Enforced by
G-DECL-01 An Event class is a producer when its channel prefix is the defining module (or stated), else a view; channels are versioned, fields are from the closed set of wire shapes (round 10: an event payload is plain data - scalars (str, int, float, bool, None, UUID, datetime, date, time, timedelta, Decimal, Enum, Literal, Any as an opaque JSON value), X | None and (round 11) unions of Literal / Enum members none of which reads another member's published value, list / tuple / set and frozenset of scalars / dict[str, T], pydantic BaseModel DTOs, and unions of DTOs only when discriminated by a Literal tag field; every other shape at any depth is refused at class definition with SANDLOOM-B015 naming the field path and the fix: (round 11) a union of scalars whose members read the same value (int | str, UUID | str, float | int, datetime | str, Literal[1] | Literal[True]) in any union_mode, strict or not, an empty wire name (alias, serialization_alias or an alias_generator returning ""), two fields with one wire name or a field named like another field's wire name in a model that reads by name, a model_config option outside the whitelist (ser_json_timedelta / temporal / inf_nan / bytes, val_json_bytes, val_temporal_unit, coerce_numbers_to_str, regex_engine, use_enum_values, polymorphic_serialization, json_encoders, json_schema_mode_override, allow_inf_nan=True, any unlisted key), a float that may be NaN or infinite (the Event base sets allow_inf_nan=False; a DTO states it), SerializeAsAny and any other node serializer, coerce_numbers_to_str on a field - so every class that defines round-trips: a property test over random classes from the whole set and from the refused shapes and options holds every one that defines to to_wire, standard JSON (no NaN), from_wire returning the same value and type at every depth, agreeing with the strict class's model_validate_json (fields set included); a rebuilt class (model_rebuild) is checked again and read by a reader of its new schema; from_wire refuses a DTO instance (it reads JSON data) -, an undiscriminated union holding a DTO or a container in any union_mode, a callable discriminator, a DTO overriding init or new, a mode=before / wrap validator over a DTO or a plain validator, a custom serializer or computed field, a wire name that does not round-trip - serialization_alias or validation_alias alone, AliasChoices, AliasPath, an alias the model does not read -, an excluded field without a default, dataclasses, TypedDicts, NamedTuples, RootModels, unparametrised generics, non-JSON scalars, non-str dict keys, sets of non-scalars, and a forward reference left unresolved), one producer per channel and module, a stated producer outside its module is refused (SANDLOOM-B015); a view never publishes (SANDLOOM-C025 at run time). A producer registers its wire schema (E.wire_schema(): the serialization schema, so a defaulted field is required, with no additionalProperties: false, so a reader accepts a newer producer's added optional field; construction stays strict), and it is snapshotted (module.snapshot.json) and exported to the shared DTO package; a change on the same .vN other than an added optional field fails before anything is written (SANDLOOM-T005), in dev and CI, at every depth: $ref / $defs are resolved, so a referenced enum's values or a nested DTO's field types, removals and newly required fields fail like top-level ones, while an added defaulted (optional) field passes at any depth - a default of any kind, literal or factory (round 7: Field(default_factory=list / dict / set / a model) carries no JSON default, so the wire schema marks every defaulted property "x-sandloom-default": true and the rule and the live schema registry read that mark; a new reader fills the field in an old payload, an old reader ignores it in a new one, while the same field added without a default still fails as newly required); a field that loses its default fails - literal or factory, top level or nested - although the serialization schema's required list is unchanged, since a producer from before the field omits it (round 8: the rule and the live schema registry, which also compares every shared $defs entry, read the lost mark), while adding a default to a required field passes; the mark is framework-owned: a json_schema_extra that sets "x-sandloom-default" (a field, a nested model, a callable) is refused at class definition (SANDLOOM-B015), so it always means "has a default"; readers ignore unknown keys at every depth (round 9: from_wire validates once with a lenient twin of the class's own validation schema, in which every extra="forbid" DTO - nested, in discriminated unions, lists, dicts, tuples and recursive definitions - ignores extra keys while extra="allow" ones keep theirs; aliases, discriminators and validators are the class's own and run once, the result and every nested value are instances of their real classes, equal to constructed ones, and a real error is raised as is; a FailFast list of any length reads; round 10: the reader is built when the class is defined and compiling it modifies no class - its model nodes name factory classes whose new allocates the real class, which pydantic-core fills and post-inits - so concurrent first decodes never cache a strict reader, nothing in the reader is strict (a strict=True DTO reads its published strings), and a self-check verifies that pydantic-core contract on a canary before the first reader is stored, failing loudly with the pydantic versions if an upgrade breaks it), so an optional field added inside a strict nested DTO is safe for its old reader, the live schema registry compares enum members as JSON values (a list or object member, mixed types), and the wire schema carries no additionalProperties: false at any depth; a recursive DTO (self- or mutually recursive) is compared once per ($ref old, $ref new) pair on a descent, so it never recurses forever and a change inside the cycle still fails; a validation constraint that narrows the accepted values fails (a bound raised / lowered or added, inclusive to exclusive, minLength / maxLength / minItems / maxItems / minProperties / maxProperties tightened or added, multipleOf added or no longer dividing the old one, pattern added or changed, uniqueItems or additionalProperties: false added; format and const changes fail as type changes), and so does one that loosens them (round 6: producers and consumers of one .vN deploy independently, so the rule holds both directions - a new reader accepts every payload an old producer may send AND an old reader accepts every payload a new producer may send: a loosened or removed bound, size, multipleOf, pattern, uniqueItems, additionalProperties: false, dependentRequired, contains or then / else / dependentSchemas constraint fails, and a model's field is never removed, open to extra keys or not - removing a required field from an extra="allow" model fails since the new producer omits it and the old reader requires it, and an optional field added to a model whose old schema forbids extra keys fails); every subschema applicator is traversed with the same rule (round 3) - a dictionary's key constraints (propertyNames) and its regex-keyed values (patternProperties: a value change under a pattern fails, an added, changed or removed key pattern fails), tuple positions (prefixItems), contains (added or narrowed) / minContains / maxContains, then / else, dependentSchemas and dependentRequired (added or narrowed), unevaluatedItems / unevaluatedProperties (narrowed, or false added) and items: false added, while any change of a not or if subschema fails; required is compared in every subschema with or without properties (round 4) - a dictionary's or a dependentSchemas / then / allOf subschema's added required name fails - an added property is compared with the schema that governed that key (a dictionary's additionalProperties or matching patternProperties, {} below an overlaying applicator), only a closed DTO (no additionalProperties, or false) may gain an optional field no old producer sent - a model open to extra keys (extra="allow": additionalProperties true or a schema) is an open object, so a field it newly declares is compared with what its old additionalProperties allowed (true: only a field accepting anything passes; round 5) -, a keyword beside a $ref is compared on its own, and a removed key pattern's keys are compared with the new additionalProperties; a differential test holds the rule to the framework validator - for every pair of a corpus of mutated pydantic wire schemas (every narrowing and loosening keyword at every position and under every applicator, plus hypothesis-composed chains; extra="allow" models, field removals, and additionalProperties true / a schema added to a DTO, included) the rule accepts, no payload sampled from the old schema fails the new one and no payload sampled from the new schema fails the old one (round 6; the sampler sends undeclared keys for a dictionary or an open model; round 8: the sampler omits defaulted fields as a producer from before them would, readers fill them, and the corpus removes defaults); the differential also runs on pairs of real Event classes with the REAL readers (from_wire of the old and new class: additions in strict, lax and open nested DTOs, lists, unions and dicts - round 10: every allowed shape - each scalar and container of scalars beside an added field, a DTO alone, optional, in lists, variadic and fixed tuples, dicts, deep containers and FailFast lists, discriminated unions alone, optional, in lists and dicts, strict, lax, open, aliased, recursive, validated and two-level nested DTOs -, and Decimal / UUID / Enum retypings, which fail (a Decimal is format: decimal in the wire schema, so its retyping to str is a type change the old Decimal reader would reject), default removals, added required and removed fields), and every such class's wire schema accepts exactly what its real reader accepts, an undeclared key at every depth included; the static view check (SANDLOOM-C025) sees fields inherited from a same-file abstract base. A field named like the Event API (channel, meta, publish, wire_schema) is refused at class definition, abstract bases included, with SANDLOOM-B015 naming the name and the fix (another name, or Field(alias=...)). tests/sdk/test_sdk_events.py::test_channel_must_be_versioned
tests/sdk/test_sdk_events.py::test_reserved_and_non_wire_fields_are_refused
tests/sdk/test_sdk_events.py::test_a_reserved_field_name_fails_at_definition
tests/sdk/test_sdk_events.py::test_role_is_derived_from_the_channel_prefix
tests/sdk/test_sdk_events.py::test_a_view_cannot_publish
tests/sdk/test_sdk_discovery.py::test_producers_are_registered_and_views_are_not
tests/sdk/test_sdk_discovery.py::test_two_producers_of_one_channel_are_refused
tests/sdk/test_sdk_generated.py::test_only_an_optional_field_may_be_added_on_the_same_version
tests/sdk/test_sdk_generated.py::test_a_referenced_enum_or_nested_type_change_is_incompatible
tests/sdk/test_sdk_generated.py::test_an_added_optional_field_passes_at_every_depth
tests/sdk/test_sdk_generated.py::test_a_recursive_dto_compares_without_recursing_forever
tests/sdk/test_sdk_generated.py::test_a_tightened_constraint_is_incompatible
tests/sdk/test_sdk_generated.py::test_a_loosened_constraint_is_incompatible_too
tests/sdk/test_sdk_generated.py::test_an_unchanged_constraint_is_compatible
tests/sdk/test_sdk_generated.py::test_a_changed_format_or_const_is_incompatible
tests/sdk/test_sdk_generated.py::test_unique_items_and_forbidden_extras_narrow_a_nested_dto
tests/sdk/test_sdk_generated.py::test_a_tightened_dictionary_key_constraint_is_incompatible
tests/sdk/test_sdk_generated.py::test_a_regex_keyed_dictionary_is_compared_per_pattern
tests/sdk/test_sdk_generated.py::test_every_applicator_follows_the_narrowing_rule
tests/sdk/test_sdk_generated.py::test_a_loosened_applicator_is_incompatible_too
tests/sdk/test_sdk_generated.py::test_an_unchanged_applicator_is_compatible
tests/sdk/test_sdk_generated.py::test_a_constraint_on_a_node_without_properties_is_compared
tests/sdk/test_sdk_generated.py::test_a_constraint_beside_a_ref_and_a_removed_key_pattern_are_compared
tests/sdk/test_sdk_schema_differential.py::test_a_change_the_rule_accepts_rejects_no_payload_either_way
tests/sdk/test_sdk_schema_differential.py::test_composed_changes_the_rule_accepts_reject_no_payload_either_way
tests/sdk/test_sdk_schema_differential.py::test_a_field_removed_from_an_open_model_breaks_its_old_readers
tests/sdk/test_sdk_schema_differential.py::test_a_loosened_constraint_breaks_its_old_readers
tests/sdk/test_sdk_schema_differential.py::test_the_corpus_covers_every_applicator_position
tests/sdk/test_sdk_schema_differential.py::test_a_field_declared_on_an_open_model_must_accept_its_old_extras
tests/sdk/test_sdk_schema_differential.py::test_a_field_added_with_a_default_factory_is_compatible_both_ways
tests/sdk/test_sdk_schema_differential.py::test_a_field_added_without_a_default_is_still_required_now
tests/integration/test_review_hardening.py::test_the_registry_accepts_a_field_added_with_a_default_factory
tests/sdk/test_sdk_schema_differential.py::test_removing_a_default_breaks_a_producer_from_before_the_field
tests/sdk/test_sdk_schema_differential.py::test_the_registry_refuses_a_removed_default
tests/sdk/test_sdk_schema_differential.py::test_an_old_strict_nested_reader_ignores_a_newer_producers_field
tests/sdk/test_sdk_schema_differential.py::test_a_user_cannot_forge_the_default_mark
tests/sdk/test_sdk_schema_differential.py::test_the_gate_agrees_with_the_real_readers
tests/sdk/test_sdk_schema_differential.py::test_the_wire_schema_is_what_the_real_reader_accepts
tests/sdk/test_sdk_event_reader.py::test_round10_concurrent_first_decodes_cache_a_lenient_reader
tests/sdk/test_sdk_event_reader.py::test_round10_an_undiscriminated_union_with_a_dto_is_refused
tests/sdk/test_sdk_event_reader.py::test_round10_a_custom_init_or_new_is_refused_at_any_depth
tests/sdk/test_sdk_event_reader.py::test_round10_a_wire_name_that_does_not_round_trip_is_refused
tests/sdk/test_sdk_event_reader.py::test_a_discriminated_or_aliased_payload_is_read_by_its_own_paths
tests/sdk/test_sdk_event_reader.py::test_a_fail_fast_list_of_any_length_reads_every_newer_item
tests/sdk/test_sdk_event_reader.py::test_a_strict_dto_reads_the_wire_form_of_every_scalar
tests/sdk/test_sdk_event_reader.py::test_a_forward_reference_is_refused_at_definition
tests/sdk/test_sdk_event_reader.py::test_the_reader_self_check_passes_on_the_pinned_pydantic
tests/sdk/test_sdk_event_reader.py::test_dicts_tuples_and_recursive_models_ignore_unknown_keys
tests/sdk/test_sdk_event_reader.py::test_a_broken_reader_contract_fails_loudly_naming_pydantic
tests/sdk/test_sdk_event_reader.py::test_an_open_model_keeps_its_extras_and_its_strict_children_ignore
tests/sdk/test_sdk_event_reader.py::test_validators_run_once_and_real_errors_are_raised
tests/sdk/test_sdk_event_reader.py::test_the_reader_returns_the_real_classes_and_construction_stays_strict
tests/sdk/test_sdk_schema_differential.py::test_every_refused_shape_raises_at_definition
tests/sdk/test_sdk_schema_differential.py::test_every_bundled_event_is_in_the_closed_set
tests/integration/test_review_hardening.py::test_enum_values_compare_as_json_values
tests/sdk/test_sdk_generated.py::test_an_incompatible_change_fails_before_anything_is_written
tests/sdk/test_sdk_generated.py::test_plan_generates_the_l1_module_files
tests/sdk/test_sdk_events.py::test_the_wire_schema_requires_defaults_and_accepts_added_fields
tests/architecture/test_api10_checks.py::test_c025_sees_fields_inherited_from_an_abstract_base
tests/sdk/test_sdk_event_reader.py::test_round11_a_union_of_overlapping_scalars_is_refused
tests/sdk/test_sdk_event_reader.py::test_round11_a_disjoint_literal_union_round_trips
tests/sdk/test_sdk_event_reader.py::test_round11_an_empty_wire_name_is_refused
tests/sdk/test_sdk_event_reader.py::test_round11_colliding_wire_names_are_refused
tests/sdk/test_sdk_event_reader.py::test_round11_a_config_that_changes_the_wire_form_is_refused
tests/sdk/test_sdk_event_reader.py::test_round11_field_options_that_change_the_wire_form_are_refused
tests/sdk/test_sdk_event_reader.py::test_round11_the_allowed_configuration_keys_round_trip
tests/sdk/test_sdk_event_reader.py::test_round11_from_wire_reads_json_data_not_instances
tests/sdk/test_sdk_event_reader.py::test_round11_a_rebuilt_class_gets_a_fresh_reader
tests/sdk/test_sdk_event_roundtrip.py::test_every_event_that_defines_round_trips
tests/sdk/test_sdk_event_roundtrip.py::test_every_allowed_event_defines_and_round_trips
tests/sdk/test_sdk_event_roundtrip.py::test_the_property_holds_on_the_round11_repros
G-DECL-02 @on(E) registers through register_event_consumer with every option (name, durable, required, dead_letter, start_from, group, database) and binds the tenant from a tenant_id: UUID field (or a named UUID field, or none); a handler whose parameter is not E, a non-async handler, a duplicate name or a bad tenant field is refused (SANDLOOM-B016); deliveries decode the payload into E (unknown fields ignored) with EventMeta attached and never part of the payload. tests/sdk/test_sdk_events.py::test_on_derives_the_tenant_binding_and_keeps_every_option
tests/sdk/test_sdk_events.py::test_on_refuses_bad_handlers
tests/sdk/test_sdk_events.py::test_callback_decodes_the_payload_and_attaches_meta
tests/sdk/test_sdk_discovery.py::test_duplicate_consumer_names_are_refused
G-DECL-03 define_module derives consumes from module-level Contract(...) references: name and required exact; methods only from direct <name>.<method>( calls on the name or a direct alias, any other use giving () (every method); merged with explicit consumes never weaker (required ORed, methods unioned or ()); a saga's contract requirements merge into consumes by name the same way. tests/sdk/test_sdk_contracts.py::test_methods_are_derived_from_direct_calls_only
tests/sdk/test_sdk_discovery.py::test_consumes_are_derived_and_merged_never_weaker
sandloom/core/tests/test_saga_requirement_merge.py::test_an_undeclared_contract_is_added_once
sandloom/core/tests/test_saga_requirement_merge.py::test_a_declared_all_methods_entry_absorbs_the_sagas
sandloom/core/tests/test_saga_requirement_merge.py::test_methods_union_required_or_and_the_declared_reason_kept
G-DECL-04 Contract(REF, optional=True).client() is None only when the provider is neither local nor bound in the deployment (ServiceNotFound / ServiceUnavailableInDeployment); a deployed provider is returned even when it will fail (503 on the call). tests/sdk/test_sdk_contracts.py::test_optional_is_none_only_when_not_deployed
tests/sdk/test_sdk_contracts.py::test_optional_returns_a_deployed_provider_even_if_it_will_fail
G-DECL-05 sandloom check SANDLOOM-C021 refuses a function-local import in a business module (typing-only imports and a marked optional dependency excepted). SANDLOOM-C021 is un-planned and active in sandloom check: the bundled modules (identity, tenancy, billing) and the examples carry no function-local import (qrcode keeps a deferred import marked # sandloom: optional-import), so the framework tree and the examples report none. tests/architecture/test_api10_checks.py::test_c021_function_local_imports
tests/architecture/test_api10_checks.py::test_every_api10_rule_has_its_appendix_a_code
tests/architecture/test_api10_checks.py::test_the_trees_pass_the_10_checks_once_unplanned
G-DECL-06 A typed event field with a wire name (Field(alias=...)) round-trips: publish() emits the alias, the registered and exported schema names it, and every reader (an @on consumer of the producer class or of a view, a projection, published(), deliver()) decodes it back from the wire key; @on and a projection bind the tenant from the field's wire name. tests/sdk/test_sdk_events.py::test_an_aliased_field_survives_publish_deliver_and_handler
tests/sdk/test_sdk_events.py::test_on_binds_the_tenant_by_its_wire_name
tests/sdk/test_sdk_testing_kit.py::test_an_aliased_field_survives_the_real_consumer_runtime
G-DECL-07 SANDLOOM-C012. A contract DTO (a model / Enum a @contract signature names, nested ones included, except classes of sandloom.shared and generated copies) is held to the closed set of event wire shapes (G-DECL-01) within the contract wire types, plus what its generated copy can carry: no parametrised generic, a default the copy can write (JSON scalars, Enum members, UUID / Decimal / date / datetime / time / timedelta, containers of them) and default_factory only list / dict / set / frozenset / tuple, Field constraints only (no custom annotated constraint, no StringConstraints transformation, no callable discriminator, no exclude_if), no Flag enum; anything else is refused when @contract decorates the provider (ContractDtoError) naming the method, the field path and the fix. The copy carries fields, types and constraints, defaults, aliases (an alias_generator resolved per field), Literal / Enum values, nested and discriminated models and the allowed model_config options - never methods or validators, which stay the provider's; over the whole set (a property test over random payloads) the copy accepts and refuses exactly what the provider's class does, in python and JSON mode, and reads accepted payloads to the same data. Contract wire dumps use each field's wire name (by_alias), so an aliased DTO round-trips. tests/sdk/test_sdk_contract_dtos.py::test_a_dto_the_copy_cannot_carry_is_refused_with_its_path
tests/sdk/test_sdk_contract_dtos.py::test_the_copy_accepts_exactly_what_the_provider_accepts
tests/sdk/test_sdk_contract_dtos.py::test_every_copied_class_is_marked_and_carries_the_wire_shape
tests/sdk/test_sdk_contract_dtos.py::test_a_validator_stays_the_providers
tests/sdk/test_sdk_contract_dtos.py::test_a_caller_receives_the_copy_equal_to_the_providers_value

Capabilities

C-TENANT

Id Statement Enforced by
C-TENANT-01 A model opts into fail-closed tenant scoping by inheriting sandloom.core.orm.TenantScoped, which declares the tenant_id column and registers it with the ORM guard. sandloom/core/tests/test_tenant_isolation.py::test_automatic_filtering_to_bound_tenant
sandloom/core/tests/test_tenant_guard_cache.py::test_new_tenant_model_invalidates_caches
sandloom/core/tests/test_ledger_runtime_pins.py::test_tenant_scoped_column_is_indexed_and_required
C-TENANT-02 Endpoints bind the request tenant with the TenantScope() dependency (configurable header and claim keys, required or optional), and workers/scripts bind one with bind_tenant(id). sandloom/core/tests/test_tenant_isolation.py::test_header_can_select_tenant_from_token_allowlist
sandloom/core/tests/test_tenant_isolation.py::test_automatic_filtering_to_bound_tenant
gap (S): TenantScope(required=False) yielding None and leaving the context unbound, and custom header/claim_keys, are untested.
C-TENANT-03 Deliberate cross-tenant admin/system access on the ORM path is available with an explicit with unscoped(): block (including bulk-by-PK updates), and logs a one-time warning when used with no tenant on an RLS database. sandloom/core/tests/test_tenant_isolation.py::test_unscoped_sees_all_tenants
sandloom/core/tests/test_tenant_guard_gaps.py::test_bulk_update_by_primary_key_allowed_when_unscoped
sandloom/core/tests/test_tenant_rls_bypass.py::test_unscoped_without_tenant_warns_once
C-TENANT-04 Single-tenant deployments run with TENANCY_MODE=single plus SINGLE_TENANT_ID, binding that canonical tenant everywhere as a strict special case of the same isolation model. sandloom/core/tests/test_tenant_isolation.py::test_single_tenant_mode_binds_canonical_tenant
sandloom/core/tests/test_open_uow.py::test_single_tenant_scope_bound_only_inside
C-TENANT-05 PostgreSQL Row-Level Security is opt-in per database instance with INFRA database option tenant_rls=true (installing the per-transaction sandloom.tenant_id hook and advertising the tenant_rls feature) plus enable_tenant_rls/disable_tenant_rls in module migrations. sandloom/core/tests/test_tenant_rls.py::test_driver_option_installs_hook
sandloom/core/tests/test_database_tenant_rls_feature.py::test_feature_set_follows_option
sandloom/core/tests/test_database_tenant_rls_feature.py::test_startup_preflight_checks_instance_options
examples/projecthub/projecthub/modules/taskboard/tests/test_rls_migration.py::test_taskboard_installs_forced_tenant_rls
C-TENANT-06 Kernel/ops code can run audited cross-tenant work through sandloom.core.drivers.system_db.system_session(actor=, reason=, database=, tenant_id=) over a separate sandloom_system login (DATABASE_SYSTEM_URL / system_url). sandloom/core/tests/test_tenant_rls_bypass.py::test_system_channel_reaches_all_tenants_and_is_audited
sandloom/core/tests/test_tenant_rls_bypass.py::test_system_session_tenant_scoped_and_rollback
C-TENANT-07 PRODUCTION_PROFILE=multi_tenant_strict is available to turn the multi-tenant trust contract (ORM guard + RLS on every database, fail-closed revocation, separate security Redis, split checks) into enforced boot and startup checks. sandloom/core/tests/test_production_profile.py::test_compliant_strict_profile_starts_and_forces_fail_closed
C-TENANT-08 Security-critical cache state can be routed to its own Redis with SECURITY_CACHE_INSTANCE, and idempotency records optionally to another with IDEMPOTENCY_CACHE_INSTANCE (default empty = follows the security cache). sandloom/core/tests/test_idempotency_cache_instance.py::test_unset_follows_the_security_cache
sandloom/core/tests/test_idempotency_cache_instance.py::test_default_keeps_guard_records_in_the_security_cache
C-TENANT-09 A deliberately global table with a tenant_id column can be exempted from the tenant-scoped-models rule via tenant_scope_exempt in architecture_rules.yml with a written reason. tests/architecture/test_tenant_scoped_models_rule.py::test_rule_runs_in_sandloom_check_and_honours_exemptions
rule tenant-scoped-models
C-TENANT-10 The TenantScoped mixin declares no plain tenant_id index on a table whose primary key, unique constraint or unique index leads with tenant_id (that key serves the lookups), and keeps it otherwise. sandloom/core/tests/test_model_indexes_migrated.py::test_tenant_index_is_dropped_when_a_unique_key_leads_with_tenant_id

C-EVENT

Id Statement Enforced by
C-EVENT-01 publish_event_versions dual-publishes one event on several channel versions (.v1 + .v2) through the outbox, re-exported from sandloom.core.di, so a channel can be rolled without starving live readers. sandloom/core/tests/test_streams_split_identity.py::test_publish_event_versions_is_reexported_for_modules
sandloom/core/tests/test_streams_split_identity.py::test_dual_publish_does_not_warn_about_starvation
sandloom/core/tests/test_uow_outbox.py::test_publish_event_versions_outside_uow_raises_unless_best_effort
gap (S): No test proves that publish_event_versions inside a UoW writes all versions as outbox rows atomically (rolled back together).
C-EVENT-02 Modules react to other modules' events through core.register_event_consumer(channel, handler, name=...) with options group=, start_from=, bind_tenant_from=, dead_letter=, durable=, required=, all forwarded by the di helper through one signature. sandloom/core/tests/test_event_consumers.py::test_module_registration_applies_event_consumer
sandloom/core/tests/test_event_consumers.py::test_register_event_consumer_has_one_signature
sandloom/core/tests/test_event_consumers.py::test_di_register_event_consumer_forwards_every_option
C-EVENT-03 sandloom streams inventory reports live consumers per channel version (flagging starved versions) and per-group lag/pending, used to decide when a .v1 reader is gone. sandloom/core/tests/test_streams_split_identity.py::test_inventory_lists_live_consumers_and_flags_starved_versions
sandloom/core/tests/test_streams_split_identity.py::test_group_lag_and_pending_are_reported
sandloom/core/tests/test_streams_split_identity.py::test_streams_cli_operations
C-EVENT-04 tenancy.member_added / member_removed / member_role_changed .v2 (tenant_id, required per-membership version) are dual-published with v1, and billing.subscription.*.v1 carry the catalog plan in data.plan. sandloom_tenancy/tests/test_registration_surface.py::test_registration_surface_is_pinned
sandloom_billing/tests/test_entitlement_intentless.py::test_subscription_events_carry_whether_the_subscription_grants

C-EFFECT

Id Statement Enforced by
C-EFFECT-01 The framework Celery worker (celery -A sandloom.core.worker) boots the composition root, takes its app from the taskqueue capability and imports .tasks plus setup_periodic_tasks of every loaded module. sandloom/core/tests/test_worker.py::test_worker_registers_loaded_module_tasks_and_beat_schedule
sandloom/core/tests/test_worker.py::test_worker_without_identity_has_no_identity_tasks
sandloom/core/tests/test_worker.py::test_worker_honours_taskqueue_instance_env
sandloom/core/tests/test_worker.py::test_non_celery_taskqueue_is_rejected
C-EFFECT-02 The sandloom.core.testing seams run_on_commit / bind_test_uow / recording_uow let tests own the transaction while still running before_commit checks then on_commit callbacks, and capture required audits. sandloom/core/tests/test_before_commit.py::test_testing_run_on_commit_runs_the_before_commit_checks_first
sandloom/core/tests/test_testing_harness.py::test_recording_uow_captures_required_audits

C-AUTH

Id Statement Enforced by
C-AUTH-01 SSO login/link over OAuth2 with one-time, TTL-bound, browser-bound CSRF state tokens (reuse and expiry refused; link requires an authenticated link state). sandloom_identity/tests/api/test_sso.py::TestSSOSecurityReal::test_state_reuse_protection_real
sandloom_identity/tests/api/test_sso.py::TestSSOSecurityReal::test_sso_state_expiration_real
sandloom_identity/tests/api/test_sso_hardening.py::test_login_csrf_victim_browser_cannot_complete_attacker_flow
sandloom_identity/tests/api/test_sso_hardening.py::test_link_flow_requires_authenticated_link_state
C-AUTH-02 TOTP 2FA with single-use codes (replay refused) and single-use backup codes, secrets encrypted at rest. sandloom_identity/tests/services/test_twofa_service.py::TestTOTPReplay::test_same_code_cannot_be_used_twice
sandloom_identity/tests/services/test_twofa_service.py::TestBackupCodes::test_backup_code_single_use
sandloom_identity/tests/services/test_twofa_service.py::TestSecretCrypto::test_undecryptable_secret_fails_loudly
C-AUTH-03 Asymmetric JWT split: sandloom keys generate produces a usable key pair (stdout or SECRETS_DIR files), identity declares itself the token issuer (issues_tokens=True), and split services verify with public keys only. sandloom/core/tests/test_asymmetric_jwt.py::test_keys_generate_prints_a_usable_pair
sandloom/core/tests/test_asymmetric_jwt.py::test_keys_generate_writes_secrets_dir_files
sandloom/core/tests/test_asymmetric_jwt.py::test_identity_declares_itself_the_token_issuer
tests/split_conformance/test_split_conformance.py::test_identity_issues_asymmetric_tokens
C-AUTH-04 Emergency stop: block_user / unblock_user from sandloom.core.di plus identity's platform-admin block / unblock / status / revoke-sessions endpoints (Idempotency-Key, audited, operator cannot block themself); the sandloom security incident CLI is area F. sandloom_identity/tests/api/test_user_safety.py::test_block_status_unblock_round_trip
sandloom_identity/tests/api/test_user_safety.py::test_block_requires_idempotency_key_and_replays_once
sandloom_identity/tests/api/test_user_safety.py::test_operator_cannot_block_or_end_own_sessions
sandloom_identity/tests/api/test_user_safety.py::test_revoke_sessions_revokes_refresh_tokens_and_audits
C-AUTH-05 Refresh-token rotation with reuse detection: each refresh rotates within the family, reuse of a rotated token revokes the whole family, a benign replay within the grace window is 409, concurrent refreshes yield one winner. sandloom_identity/tests/services/test_refresh_rotation.py::test_refresh_rotates_within_family
sandloom_identity/tests/services/test_refresh_rotation.py::test_reusing_rotated_token_revokes_whole_family
sandloom_identity/tests/services/test_refresh_rotation.py::test_replay_within_grace_window_is_409_without_revocation
sandloom_identity/tests/services/test_refresh_rotation.py::test_concurrent_refresh_only_one_wins
C-AUTH-06 Composable authz algebra (require / enforce / And / Or / Not over Perm, Role, Owner, Subscribed, SubscriptionActive, Credits, Entitled; the billing atoms take a required of=) as the single enforcement path. tests/integration/test_authz_algebra.py::test_perm_and_role_membership
tests/integration/test_authz_algebra.py::test_and_or_not
tests/integration/test_authz_algebra.py::test_owner_loads_resource_once
tests/integration/test_access_subsystem.py::test_enforce_allows_and_returns_decision
C-AUTH-07 subject_commit_check(request), at_commit() (an explicit commit obligation on a non-write route; replaces revocation_at_commit()) and before_commit(callback) as reusable kernel hooks; the re-check runs before the COMMIT of every committing unit of the request and is automatic on writes and writing GETs. sandloom/core/tests/test_before_commit.py::test_checks_run_in_order_after_the_handler_and_before_commit
sandloom/core/tests/test_before_commit.py::test_subject_check_passes_without_a_mark_and_refuses_after_one
sandloom_tenancy/tests/test_accept_authority_at_commit.py::test_unchanged_session_accepts_and_commits
tests/integration/test_route_runtime.py::test_a_get_that_writes_is_rechecked_and_a_reading_get_is_not
C-AUTH-08 Billing operation intents with a periodic PSP reconciler (billing.tasks.reconcile_operation_intents) for class-F checkout / portal. sandloom_billing/tests/test_intent_reconcile.py::test_reconcile_task_body_reports_counts
sandloom_billing/tests/test_operation_intents.py::test_checkout_persists_an_issued_intent_keyed_to_the_psp_session
C-AUTH-09 current_service is a service authenticator: it accepts the internal token (404 otherwise, also for a wrong token), resolves the route's auth as "service" and returns the service principal. tests/integration/test_request_boundaries.py::test_current_service_authenticates_a_service_principal

C-ROUTE

Id Statement Enforced by
C-ROUTE-01 no_idempotency(reason=) opts a write route out of the automatic idempotency guard; an explicit idempotency_guard(...) keeps all its parameters (scope, durable, store_response, fail_closed) and its misuse checks. tests/integration/test_automatic_idempotency.py::test_without_a_request_unit_the_guard_is_the_fail_closed_in_flight_form
tests/integration/test_automatic_idempotency.py::test_mode_x_re_runs_authorization_and_the_handler
sandloom/core/tests/test_durable_idempotency_guard.py::test_durable_guard_mode_and_misuse
C-ROUTE-02 sensitive(risk) is the escape hatch for a hand-rolled authority check: it resolves {risk: ("sensitive()",)}; sensitive("security") derives a 300 s step-up; an unknown risk is refused. tests/integration/test_sensitive_risk.py::test_sensitive_marker_and_effect_tags_resolve_with_their_source
tests/architecture/test_operation_classes.py::test_unknown_class_is_refused
C-ROUTE-03 sandloom check untagged-sensitive (SANDLOOM-C022, warning) reports a money command without risk="money", a credential / session change (revocation, block, unblock) without @sensitive_effect, and a write route that decides an authority by hand (an authority call or a 403 in its body, at_commit(), a membership change behind a member-level gate) without sensitive(risk); a route behind a framework admin gate (route, parameter or router dependencies) or public() is not reported. On the framework tree the mixed-authority routes and owning administration effects declare admin risk; the shared identity session-revocation mechanism has one local, reasoned waiver because its caller owns the authorization and credential-step-up policy. tests/architecture/test_api10_checks.py::test_c022_untagged_money_and_security_effects
tests/architecture/test_api10_checks.py::test_c022_hand_rolled_authority_routes
tests/architecture/test_api10_checks.py::test_c022_bundled_operations_have_no_unreviewed_sensitive_effects

C-UNIT

Id Statement Enforced by
C-UNIT-01 db() from a task that does not own the unit is SANDLOOM-R013; child tasks open their own unit with open_uow(). tests/integration/test_units_and_admission.py::test_a_session_passed_to_another_task_is_detected
tests/integration/test_units_and_admission.py::test_child_db_before_the_parent_creates_nothing_then_parent_owns
C-UNIT-02 Best-effort footgun detection (detection, not a guarantee): with DB_SESSION_OWNER_CHECKS on (the test environment's default; off in production, kept off the hot path by the performance gate) a session object used from another task is detected on its kernel-session methods and at the cursor; db() from a non-owner task is always refused (C-UNIT-01). Known undetectable forms: in-place mutation of a mutable column value (JSON) and attribute assignment on a loaded instance (no attribute listeners, kept off the hot path by the performance gate). tests/integration/test_units_and_admission.py::test_a_session_passed_to_another_task_is_detected
tests/integration/test_units_and_admission.py::test_in_place_json_mutation_is_a_known_undetectable_form

C-SPLIT

Id Statement Enforced by
C-SPLIT-01 The same code deploys as a monolith or as any subset of identity/tenancy/billing purely via SANDLOOM_MODULES + SERVICE_BINDINGS: bound subsets answer the request script identically to the monolith, unbound ones identically or with an explicit 404/501/503 (never a 500 or a different 2xx), on both transports. tests/split_conformance/test_split_conformance.py::test_subset_conforms_to_monolith
tests/split_conformance/test_split_conformance.py::test_monolith_script_matches_expectations
tests/split_conformance/test_split_conformance.py::test_every_process_is_ready
C-SPLIT-02 core.get_service resolves local-first: a co-deployed provider wins over a SERVICE_BINDINGS proxy, otherwise a generated RPC proxy is returned (reused until the binding changes), and with neither it raises ServiceNotFound so consumers degrade explicitly. sandloom/core/tests/test_service_rpc.py::test_core_get_service_prefers_local_over_remote_binding
sandloom/core/tests/test_service_rpc.py::test_get_service_degrades_explicitly_when_unavailable
sandloom/core/tests/test_service_rpc.py::test_get_service_reuses_the_remote_proxy_until_the_binding_changes
sandloom/core/tests/test_contract_runtime.py::test_local_contributor_wins_over_remote_binding
C-SPLIT-03 Every bundled module subset (and each module alone through create_app + lifespan) boots and becomes ready, refusing only with the documented ContractBindingError for a required consumed contract. sandloom/core/tests/test_module_subset.py::test_module_subset_boots_alone
sandloom/core/tests/test_module_subset.py::test_each_single_module_subset_is_ready
sandloom/core/tests/test_module_subset.py::test_full_monolith_set_loads
sandloom_identity/tests/test_module_wiring.py::test_module_boots_alone
sandloom_tenancy/tests/test_module_wiring.py::test_module_boots_alone
sandloom_billing/tests/test_module_wiring.py::test_module_boots_alone
C-SPLIT-04 Remote peers never feed /health; a down peer fails /ready only for a binding marked "required": true, and required probes run concurrently, each capped at SERVICE_PROBE_TIMEOUT_SECONDS. sandloom/core/tests/test_split_readiness.py::test_required_peer_down_fails_ready_but_never_health
sandloom/core/tests/test_split_readiness.py::test_down_peer_does_not_fail_health_or_ready_by_default
sandloom/core/tests/test_split_readiness.py::test_required_probes_run_concurrently_capped_and_authenticated
C-SPLIT-05 SERVICE_BINDINGS is strictly validated at startup: only base_url/url (absolute http(s)), timeout_seconds, critical, required and service_id are accepted; unknown keys (typos) and an internal_token are errors. sandloom/core/tests/test_split_readiness.py::test_invalid_service_binding_is_rejected
sandloom/core/tests/test_split_readiness.py::test_valid_service_bindings_accepted
sandloom/core/tests/test_rpc_allowlist.py::test_a_binding_carries_no_token
C-SPLIT-06 The shipped microservices profile binds exactly each service's consumed-but-not-local contracts (identity binds the remote claims contracts), derives RPC_ALLOWED_CALLS from those bindings, and every service keys its responses with callers pinning providers. sandloom/core/tests/test_deploy_profiles.py::test_microservices_bindings_are_exactly_the_consumed_contracts
sandloom/core/tests/test_deploy_profiles.py::test_token_issuer_binds_every_remote_claims_contributor
sandloom/core/tests/test_deploy_profiles.py::test_rpc_allowlist_is_derived_from_bindings
sandloom/core/tests/test_deploy_profiles.py::test_every_service_keys_its_responses_and_callers_pin_providers
C-SPLIT-07 A token issuer deployed alone collects other modules' claims over RPC through their .claims.v1 binding; a critical remote contributor failure aborts minting with 503. sandloom/core/tests/test_contract_runtime.py::test_identity_alone_collects_remote_claims
sandloom/core/tests/test_contract_runtime.py::test_critical_remote_contributor_failure_is_503

C-RPC

Id Statement Enforced by
C-RPC-01 RPC_TRANSPORT selects the shared outbound client: aiohttp (default, production) or httpx (fallback, for egress proxies); both serve calls, including TLS to a self-signed peer trusted via SSL_CERT_FILE. sandloom/core/tests/test_rpc_transports.py::test_rpc_transport_setting_selects_the_shared_client
sandloom/core/tests/test_rpc_transport_compat.py::test_the_fallback_client_serves_calls
sandloom/core/tests/test_rpc_transports.py::test_tls_self_signed_trusted_via_ssl_cert_file
C-RPC-02 The aiohttp client keeps a bounded per-peer keep-alive pool (RPC_POOL_MAX_CONNECTIONS, waits bounded by the call timeout and observed) with idle eviction after RPC_POOL_IDLE_SECONDS. sandloom/core/tests/test_rpc_transports.py::test_keep_alive_connection_is_reused
sandloom/core/tests/test_rpc_transports.py::test_pool_is_bounded_per_peer_and_waits_are_observed
sandloom/core/tests/test_rpc_transports.py::test_pool_wait_is_bounded_by_the_timeout
sandloom/core/tests/test_rpc_transports.py::test_idle_connections_are_evicted
C-RPC-03 sandloom keys generate --purpose rpc --service <id> emits a separate Ed25519 + X25519 pair (with RPC_SERVICE_KEYS / RPC_SERVICE_DH_KEYS entries) that validates as a working configuration, and --purpose rpc-dh emits the X25519 pair only (optionally into --out-dir). sandloom/core/tests/test_rpc_allowlist.py::test_keys_generate_rpc_validates
sandloom/core/tests/test_rpc_pair_mac.py::test_keys_generate_rpc_dh
C-RPC-04 sandloom keys allowlist --bindings <svc>='<SERVICE_BINDINGS>' prints the RPC_ALLOWED_CALLS derived from the services' bindings. sandloom/core/tests/test_rpc_allowlist.py::test_allowlist_is_derived_from_service_bindings
C-RPC-05 sandloom.core.contract_testing.assert_contract_conformance(core, name, [ContractCase(...)]) lets an app prove a contract answers identically locally and through a generated proxy (in-process ASGI or a real aiohttp/httpx socket), also under the pair MAC. sandloom/core/tests/test_contract_parity.py::test_local_and_rpc_results_and_errors_are_identical
sandloom/core/tests/test_rpc_pair_mac.py::test_contract_conformance_holds_under_service_mac

C-INFRA

Id Statement Enforced by
C-INFRA-01 A new backend is a driver, not a core edit: inside the kernel, any provider implementing a contract plugs in through the registry (a non-SQLAlchemy database driver included), and a CapabilitySpec adds a capability. A driver package can do so for the contracts sandloom.driverkit publishes and for capabilities of its own. sandloom/core/tests/test_infra_multi_database.py::test_custom_non_sqlalchemy_database_driver
sandloom/core/tests/test_extra_capabilities.py::test_plugin_adds_capability_and_driver
sandloom/core/tests/test_capabilities.py::test_register_and_query_capability
C-INFRA-02 Driver packages are found by name in the sandloom.drivers entry-point group and imported only when INFRA selects them, so load order does not matter and an unselected package runs no code. A selected name with two providers, or one that cannot be used, stops startup (SANDLOOM-B021, B022). sandloom/core/tests/test_driverkit.py::test_a_selected_driver_is_found_by_name
sandloom/core/tests/test_driverkit.py::test_an_unselected_package_is_never_imported
sandloom/core/tests/test_driverkit.py::test_a_capability_in_another_package_loads_in_any_order
sandloom/core/tests/test_driverkit.py::test_two_providers_of_a_selected_name_are_refused
sandloom/core/tests/test_driverkit.py::test_a_package_cannot_take_over_a_built_in_driver
sandloom/core/tests/test_driverkit.py::test_a_selected_driver_that_cannot_be_used_stops_startup
C-INFRA-03 With no extra config one default instance per capability is synthesized from the flat settings; the INFRA JSON env var adds named instances or overrides drivers/options. sandloom/core/tests/test_infra_config.py::test_defaults_synthesized_for_every_capability
sandloom/core/tests/test_infra_config.py::test_infra_overlay_adds_named_instance
sandloom/core/tests/test_infra_config.py::test_infra_overlay_overrides_default_options
sandloom/core/tests/test_infra_config.py::test_new_instance_requires_driver
C-INFRA-04 Several databases run at once as independent engines/pools, each with its own UoW and its own transactional outbox; with several active sessions the target database must be named (database=...). sandloom/core/tests/test_infra_multi_database.py::test_two_databases_are_independent_and_both_work
sandloom/core/tests/test_post_commit_dispatcher.py::test_marks_are_coalesced_into_one_update_per_database
sandloom/core/tests/test_review_fixes_v2.py::test_resolve_scoped_session_ambiguous_without_database
sandloom/core/tests/test_testing_harness.py::test_bind_test_uow_targets_the_named_database
C-INFRA-05 Endpoints resolve infrastructure by logical name through DI: Annotated[AsyncSession, Database("analytics")] for a named database and Capability(cap, Contract, name) for any capability. sandloom/core/tests/test_uow_commits_before_response.py::test_dbsession_and_database_markers_use_function_scope
sandloom/core/tests/test_async_dependencies.py::test_dependency_factories_are_async
sandloom/core/tests/test_kernel_review_regressions.py::test_revocation_at_commit_covers_named_db_in_either_dependency_order
gap (S): no test resolves a Capability(...) dependency in a request and asserts it yields the named provider (or the contract/feature error)
C-INFRA-06 document_store, graph_store, workflow, search, lock and secrets ship as CapabilitySpecs with contracts, marked stability=experimental, registered at bootstrap without a core driver; a plugin supplies the driver. sandloom/core/tests/test_extra_capabilities.py::test_extra_capabilities_are_specs
sandloom/core/tests/test_extra_capabilities.py::test_extra_capabilities_are_marked_experimental
sandloom/core/tests/test_extra_capabilities.py::test_known_capability_without_driver_gives_clear_error
sandloom/core/tests/test_extra_capabilities.py::test_capability_spec_rejects_unknown_stability
C-INFRA-07 notifier().send(Notification(...)) works with the zero-dependency log driver by default (never logging the raw recipient; no delivery features advertised in prod unless opted in) and with NOTIFIER_BACKEND=smtp for email (STARTTLS verifies the certificate; login without TLS refused in prod). sandloom/core/tests/test_notifications.py::test_default_resolves_to_log_notifier
sandloom/core/tests/test_notifications.py::test_log_notifier_never_logs_the_raw_recipient
sandloom/core/tests/test_notifications.py::test_log_driver_advertises_no_delivery_features_in_prod
sandloom/core/tests/test_notifications.py::test_smtp_starttls_verifies_the_server_certificate
C-INFRA-08 The object store is key-addressed (put(key, data, content_type)/get/delete/url): callers pass a logical key + bytes, never a host path; traversal and symlink escapes out of the root are rejected. sandloom/core/tests/test_infra_hardening.py::test_objectstore_roundtrip
sandloom/core/tests/test_infra_hardening.py::test_objectstore_rejects_traversal
sandloom/core/tests/test_infra_hardening.py::test_objectstore_rejects_symlink_escaping_root
C-INFRA-09 The cache capability is operation-based (get/set/add/delete/incr, cas compare_and_set/compare_and_delete, fused incr_window/incr_many/set_nx_get/set_max) and never hands modules a raw client. sandloom/core/tests/test_infra_hardening.py::test_cache_provider_operations
C-INFRA-10 /diagnostics/infra reports configured capabilities, instances, stability, registered drivers and per-instance health (registry.describe()). sandloom/core/tests/test_infra_registry.py::test_describe_snapshot
sandloom/core/tests/test_capabilities.py::test_describe_includes_capabilities
sandloom/core/tests/test_extra_capabilities.py::test_describe_renders_stability
sandloom/core/tests/test_infra_registry.py::test_health_all_and_aclose_all
gap (S): no test calls the /diagnostics/infra endpoint itself (only registry.describe(); the route appears only in the operation-class map)
C-INFRA-11 core.capability(cap, name) is the universal accessor and core.require_capability also enforces features; the typed sugar (cache/database/objectstore/taskqueue/notifier) routes through it. sandloom/core/tests/test_capabilities.py::test_facade_capability_and_require_capability
sandloom/core/tests/test_infra_registry.py::test_resolves_and_caches_instances
C-INFRA-12 The relational driver contract of the unit machinery: LazyUnitOfWorkContract (open_lazily / close) backs the lazy request unit (refused otherwise), the commit hooks ask DatabaseProvider.has_pending_changes(session) (no SQLAlchemy call in the kernel hooks), and the SQLAlchemy driver implements both and advertises write_admission. Verified on PostgreSQL/SQLAlchemy only. sandloom/core/tests/test_relational_driver_contract.py::test_the_lazy_unit_needs_the_lazy_uow_contract
sandloom/core/tests/test_relational_driver_contract.py::test_the_commit_hooks_ask_the_driver_for_pending_changes
sandloom/core/tests/test_relational_driver_contract.py::test_the_sqlalchemy_driver_reports_unflushed_orm_changes
tests/integration/test_write_store.py::test_a_database_hosting_request_units_needs_write_admission

C-MODULE

Id Statement Enforced by
C-MODULE-01 sandloom add module generates a compliant, parseable module skeleton (ORM-free api, versioned event channels, TenantScoped example, declarative register), namespace-portable, refusing bad names and overwrites. sandloom/core/tests/test_module_generation.py::test_add_module_generates_a_compliant_parseable_skeleton
sandloom/core/tests/test_module_generation.py::test_add_module_refuses_bad_names_and_overwrites
C-MODULE-02 sandloom new generates a consumer-owned app project (app/asgi on the kernel create_app, deploy files, CI + pre-commit running sandloom check, a manifest enforced by sandloom check) whose own tests and lint pass out of the box. sandloom/core/tests/test_project.py::test_generate_minimal_project
sandloom/core/tests/test_project.py::test_generated_project_passes_its_own_tests
sandloom/core/tests/test_project.py::test_generated_manifest_is_enforced_by_sandloom_check
sandloom/core/tests/test_project.py::test_generated_ci_and_precommit_run_sandloom_check
C-MODULE-03 Packaging: a generated app depends on the sandloom distribution (compatible range, with its Redis/Celery drivers) and on one ecosystem package per module chosen with --with (sandloom-identity, ...: compatible ranges, no exact pin), every sandloom.* import of a generated project ships in its declared distributions, and the app reuses the kernel factory. sandloom/core/tests/test_generated_project_packaging.py::test_generated_imports_ship_in_its_dependencies
sandloom/core/tests/test_project.py::test_apps_depend_on_sandloom_and_each_chosen_module_package
sandloom/core/tests/test_generated_project_packaging.py::test_minimal_app_uses_the_kernel_factory
sandloom/core/tests/test_project.py::test_asgi_reuses_framework_factory
C-MODULE-04 define_module(name, setup=..., router=..., events=...) calls setup(core) for contracts/consumers/projections, accepts explicit router/events overrides, supports modules without api/events, and infers the package from the caller frame as a fallback. sandloom/core/tests/test_define_module.py::test_frame_inference_and_setup_hook
sandloom/core/tests/test_define_module.py::test_explicit_router_and_events_override_discovery
sandloom/core/tests/test_define_module.py::test_module_without_api_or_events
C-MODULE-05 sandloom check [--only rule,...] [--list-rules] runs the stable set of architecture analyzers against an architecture_rules.yml manifest (framework or app), exiting 0 ok / 1 violations / 2 usage. tests/architecture/test_archcheck_library.py::test_rule_ids_are_stable
tests/architecture/test_archcheck_library.py::test_cli_help_lists_every_rule
sandloom/core/tests/test_project.py::test_sandloom_check_cli_exit_codes
tests/architecture/test_archcheck_library.py::test_cli_rejects_missing_module_directory
rule module-boundaries
rule framework-imports
rule raw-provider
rule infra-imports
rule kernel-handles
rule uow-boundary
rule best-effort-effects
rule get-core-await
rule endpoints-outside-api
rule orm-leakage
rule schema-orm-imports
rule test-imports
C-MODULE-06 SANDLOOM_MODULES selects the module subset a process loads; every subset boots and becomes ready on its own, a blacklisted module is excluded, and a subset whose only module failed is not ready. sandloom/core/tests/test_module_subset.py::test_module_subset_boots_alone
sandloom/core/tests/test_module_subset.py::test_each_single_module_subset_is_ready
sandloom/core/tests/test_module_subset.py::test_blacklist_excludes_module
sandloom/core/tests/test_module_subset.py::test_subset_whose_only_module_failed_is_not_ready
C-MODULE-07 MODULE_REQUIRED makes a module's load failure abort startup; when empty, every module listed explicitly in SANDLOOM_MODULES is required, an explicit MODULE_REQUIRED wins, and a required dependent of a failed module aborts. sandloom/core/tests/test_split_readiness.py::test_explicitly_listed_modules_are_required_by_default
sandloom/core/tests/test_split_readiness.py::test_explicit_module_required_wins
sandloom/core/tests/test_lifecycle_critical.py::test_entrypoint_fail_fast_honors_module_required
sandloom/core/tests/test_loader_migrations_hardening.py::test_required_dependent_of_failed_module_aborts
C-MODULE-08 The sandloom.core.testing pytest plugin, activated explicitly with addopts -p sandloom.core.testing.plugin (no pytest11 entry point), loads .env.test before any conftest imports settings, refuses a non-test database and provides the client/db/make_headers fixtures, so app modules need no conftest. sandloom/core/tests/test_testing_plugin.py::test_env_test_is_loaded_before_any_conftest_imports_settings
sandloom/core/tests/test_testing_plugin.py::test_refuses_a_non_test_database
sandloom/core/tests/test_project.py::test_generated_app_activates_the_test_plugin
tests/architecture/test_ledger_static_pins.py::test_no_distribution_registers_a_pytest11_plugin
C-MODULE-09 migrations/env.py is run_module_migrations(file): the package, .models and the _ table prefix are derived from the env.py location, keywords override, and a non-package location is a clear error. sandloom/core/tests/test_run_module_migrations.py::test_conventions
sandloom/core/tests/test_run_module_migrations.py::test_explicit_overrides
sandloom/core/tests/test_run_module_migrations.py::test_package_is_derived_from_the_env_file
sandloom/core/tests/test_run_module_migrations.py::test_not_a_package_is_a_clear_error
C-MODULE-10 A route policy's atoms add the contracts they resolve to the module's derived consumes as optional requirements (SubscriptionActive / Credits the entitlements contract, Entitled the fact owner's facts.v1, Subscribed billing.facts.v1, OrgRole the membership contract). tests/sdk/test_sdk_discovery.py::test_route_policies_add_the_contracts_their_atoms_call
C-MODULE-11 identity.contact.v1 answers a user's contact details (email, name, email_verified, is_active) by id, locally and over RPC alike, so a module that writes to users needs no PII in events and no identity edit. sandloom_identity/tests/test_contact_contract.py::test_get_contact_and_get_contacts
sandloom_identity/tests/test_contact_contract.py::test_contact_contract_conforms
C-MODULE-12 Module ecosystem: a module distribution registers its module in the sandloom.modules entry-point group (name = import path); its entry-point name is its module name (tables, migration chain, channels, contracts), SANDLOOM_MODULES / MODULE_REQUIRED / deploy profiles may name it by that name or by import path, and a requested name with two providers (two installed packages, or an installed package and a local module) is refused before anything loads (SANDLOOM-B020), naming both and the fix. sandloom/core/tests/test_module_names.py::test_module_name_is_the_entry_point_name_else_the_last_segment
sandloom/core/tests/test_module_names.py::test_entry_point_names_and_import_paths_resolve_alike
sandloom/core/tests/test_module_names.py::test_two_packages_providing_one_requested_name_are_refused
sandloom/core/tests/test_module_names.py::test_a_local_module_named_like_an_installed_one_is_refused
sandloom/core/tests/test_module_names.py::test_the_loader_refuses_an_ambiguous_module_before_loading_anything
sandloom/core/tests/test_module_names.py::test_configuration_names_installed_modules_by_entry_point_name
tests/sdk/test_sdk_module_names.py::test_the_sdk_owner_rule_agrees_with_the_kernel
C-MODULE-13 Distributions are layered and work installed alone: no module a distribution ships imports (module level, function-local or a constant-string dynamic import) a module of a distribution it does not depend on, nor a first-party module no distribution ships, nor an undeclared third-party package outside an ImportError guard; pip install sandloom alone (no module package, no Redis/Celery) runs every sandloom subcommand's --help, sandloom docs and sandloom new; sandloom[standard] composes the installed modules; an app's sandloom check treats an installed module package as another module. tests/architecture/test_distribution_imports.py::test_a_kernel_import_of_a_module_package_is_caught
tests/architecture/test_distribution_imports.py::test_the_lazy_cli_import_of_an_absent_distribution_is_caught
tests/architecture/test_distribution_imports.py::test_an_unshipped_first_party_module_is_caught
tests/architecture/test_distribution_imports.py::test_undeclared_third_party_is_caught_unless_optional
tests/integration/test_package_distributions.py::test_every_distribution_installs_and_runs_alone
tests/architecture/test_archcheck_library.py::test_framework_imports_catch_from_sandloom_import_and_dynamic_aliases
tests/architecture/test_archcheck_library.py::test_test_imports_is_a_whitelist

C-MIGRATE

Id Statement Enforced by
C-MIGRATE-01 sandloom migrate upgrade-all --apply applies the core chain to every hosting database and each loaded module's chain to its database. sandloom/core/tests/test_project.py::test_upgrade_all_on_freshly_generated_project
tests/integration/test_migrations_per_chain.py::test_migrations_cli_loads_modules_with_chains
C-MIGRATE-02 sandloom migrate upgrade-module --apply applies one module's chain to its database(s). tests/integration/test_migration_revision_cli.py::test_revision_autogenerate_and_upgrade_for_app_module
C-MIGRATE-03 sandloom migrate upgrade-core [--database x|--all-databases] --apply [--lock-timeout S] applies the core infra chain to a named database. sandloom/core/tests/test_ops_cli.py::test_cli_lock_timeout_flag_reaches_the_lock
gap (S): Only flag plumbing is tested; no test runs upgrade-core --database against a second database.
C-MIGRATE-04 sandloom migrate revision -m msg [--autogenerate] creates a revision in that module's own chain (its migrations/, version table and database), for app modules via SANDLOOM_COMPOSITION_ROOT; an unknown module fails cleanly. tests/integration/test_migration_revision_cli.py::test_revision_autogenerate_and_upgrade_for_app_module
tests/integration/test_migration_revision_cli.py::test_revision_unknown_module_fails_cleanly
C-MIGRATE-05 sandloom migrate databases lists the configured database instances (default plus INFRA-declared ones). sandloom/core/tests/test_migrations_multidb.py::test_lists_default_database
sandloom/core/tests/test_migrations_multidb.py::test_lists_named_databases_from_infra
sandloom/core/tests/test_migrations_multidb.py::test_resolve_unknown_database_raises
C-MIGRATE-06 sandloom migrate extract-module moves one module's existing data out of a shared database into its own (fence, migrate target, copy, verify, outbox/inbox hand-off, group-owner transfer), enabling monolith -> microservices with data. tests/extraction/test_extraction_e2e.py::test_monolith_to_microservices_with_data
sandloom/core/tests/test_module_extraction.py::test_extract_crash_resume_verify_and_cleanup
sandloom/core/tests/test_module_extraction.py::test_spec_owns_exactly_the_module_tables
C-MIGRATE-07 sandloom migrate ensure-outbox [--all-databases] --apply idempotently creates the shared core-infra tables for dev/repair. sandloom/core/tests/test_migrations_multidb.py::test_ensure_outbox_creates_table_idempotently
C-MIGRATE-08 A module's migrations/env.py is the one-liner run_module_migrations(file), deriving package, table prefix and version table by convention with explicit overrides. sandloom/core/tests/test_run_module_migrations.py::test_conventions
sandloom/core/tests/test_run_module_migrations.py::test_explicit_overrides
sandloom/core/tests/test_run_module_migrations.py::test_package_is_derived_from_the_env_file
C-MIGRATE-09 sandloom migrate new autogenerates against a scratch database with every chain at head, so it needs no configured database at head (--against-database keeps that comparison) and loads the project's .env.test like sandloom test. tests/integration/test_migration_revision_cli.py::test_new_autogenerates_against_a_scratch_database

C-PROJ

Id Statement Enforced by
C-PROJ-01 monotonic_upsert / monotonic_update apply a projection change only when the stored version is NULL or older than the incoming one, so at-least-once / out-of-order delivery never rolls a row back (tombstones block resurrection, and a tombstone purge never deletes one between an apply's watermark check and its write, as the apply holds the watermark row FOR SHARE until it commits). sandloom/core/tests/test_projection_upsert_guards.py::test_null_stored_version_is_updatable
sandloom/core/tests/test_projection_upsert_guards.py::test_tombstone_prevents_old_rebuild_from_resurrecting_deleted_entity
sandloom/core/tests/test_projections.py::test_consumer_applies_and_is_monotonic_and_idempotent
tests/sdk/test_sdk_projection_purge_race.py::test_a_purge_racing_an_apply_never_resurrects_a_relational_row
C-PROJ-02 Projection rows carry tenant_id: monotonic_upsert requires it and raises TenantMismatchError if it differs from the bound tenant; updates are scoped to the bound tenant. sandloom/core/tests/test_projection_upsert_guards.py::test_upsert_requires_tenant_and_never_crosses_tenants
sandloom/core/tests/test_projection_upsert_guards.py::test_update_is_scoped_to_bound_tenant
C-PROJ-03 sandloom projection list | rebuild [--tenant ] rebuilds a projection from source modules' read contracts, running startup tasks only (no consumers, no relay). sandloom/core/tests/test_projections.py::test_rebuild_cli_does_not_start_consumers_or_relay
examples/projecthub/projecthub/modules/taskboard/tests/test_projection.py::test_rebuild_refreshes_stale_project_fields
gap (S): --tenant scoping of a rebuild is not exercised by a test.
C-PROJ-04 monotonic_upsert(insert_only=...) seeds columns this source does not own on INSERT and never touches them on UPDATE. gap (S): No test (framework or examples) uses insert_only; a unit test proving INSERT seeds and UPDATE preserves the column is missing.
C-PROJ-05 A read module registers a QueryProjectionDescriptor, per-channel ProjectionConsumerDescriptors (durable consumers namespaced by the module) and a ProjectionRebuilder; a consumer for an unknown projection or a duplicate registration is rejected. sandloom/core/tests/test_projections.py::test_registry_register_and_duplicate
sandloom/core/tests/test_projections.py::test_module_registration_rejects_consumer_for_unknown_projection
sandloom/core/tests/test_projections.py::test_projection_consumer_group_is_namespaced_by_module
sandloom/core/tests/test_projections.py::test_start_projection_consumers_wires_and_delivers
C-PROJ-06 The projecthub taskboard pattern (version-guarded local source snapshot locked by both writers) keeps a denormalised field correct when a source event races a projection insert, and the projection is served by one query. examples/projecthub/projecthub/modules/taskboard/tests/test_projection.py::test_project_update_racing_a_task_insert_is_not_lost
examples/projecthub/projecthub/modules/taskboard/tests/test_projection.py::test_project_update_waits_for_an_uncommitted_task_insert
examples/projecthub/projecthub/modules/taskboard/tests/test_projection.py::test_projection_converges_and_is_served_by_one_query
C-PROJ-07 Experimental in 1.0 (owner decision 2026-10-26; relational model= targets are the supported 1.0 path). Projection(store=("document_store", name)) targets a store whose driver advertises conditional_write (put_if_newer / tombstone_if_newer, refused otherwise) and is read by View(...); the in-memory document_store driver (memory) advertises conditional_write and tenant_native and passes the conformance kit. tests/sdk/test_sdk_projection_ops.py::test_a_store_projection_converges_and_reads_skip_tombstones
tests/sdk/test_sdk_projection_ops.py::test_a_store_without_conditional_writes_is_refused
sandloom/core/tests/test_projection_kernel.py::test_the_memory_driver_advertises_and_passes_the_kit
C-PROJ-08 rebuild(name, tenant_id=) and sandloom projection rebuild run the projection's rebuilder with the rebuild marked in its watermark row (purges refuse meanwhile), without the consumer runtime; rebuild_all covers the tenants the projection holds, rebuild_entity(":") one aggregate, rebuild_from may name (contract, method); blue_green=True runs in place (no 1.0 store swaps an alias) and says so in the result. sandloom/core/tests/test_projections.py::test_rebuild_cli_does_not_start_consumers_or_relay
tests/sdk/test_sdk_projection_ops.py::test_rebuild_entity_applies_one_aggregate_and_checks_its_ref
tests/sdk/test_sdk_projection_ops.py::test_a_blue_green_rebuild_runs_in_place_and_says_so
C-PROJ-09 A projection source group names its tenant field (tenant_from=, e.g. organization_id) and the consumer binds that tenant; a source whose version field is optional or which lacks the tenant field is refused at definition time with the alternative. tests/sdk/test_sdk_projections.py::test_a_source_names_its_tenant_field
tests/sdk/test_sdk_projections.py::test_an_optional_version_is_refused_with_the_alternative

C-MSTORE

Id Statement Enforced by
C-MSTORE-01 Delivery lattice laws (sandloom.core.guarantee_lattice): a guarantee's delivery set is closed under atomic => no_loss and no_dup; the meet is idempotent, commutative and associative, FULL is its unit on deliveries, and unknown properties are refused. sandloom/core/tests/test_guarantee_lattice.py::test_atomic_implies_no_loss_and_no_dup
sandloom/core/tests/test_guarantee_lattice.py::test_the_meet_is_a_semilattice_and_never_strengthens
sandloom/core/tests/test_guarantee_lattice.py::test_unknown_properties_are_refused
C-MSTORE-02 Monotonicity: combining effects (the meet) never raises a guarantee above either operand. sandloom/core/tests/test_guarantee_lattice.py::test_the_meet_is_a_semilattice_and_never_strengthens
C-MSTORE-03 Labels are derived from the mechanism, one scenario per row of the label table: only a write-store write is atomic; an outbox event is at-least-once (no_loss only with a durable bus); a durable consumer effect is no_dup only with the inbox in its database and the inbox outliving the stream; an effect in another store is no_dup only through a conditional write; a command is no_dup only with a database command log whose TTL covers the retries (never the cache fallback); consumer delivery is always qualified (parks_*); a label is shown only where it deviates. sandloom/core/tests/test_guarantee_lattice.py::test_each_row_of_the_label_table
sandloom/core/tests/test_guarantee_lattice.py::test_a_label_is_shown_only_when_it_deviates
C-MSTORE-04 A conditional_write store passes conditional_write_conformance (older and duplicate versions never apply, tombstones are versioned and never resurrected, tenants isolated); the in-memory driver passes, a last-write-wins store fails. sandloom/core/tests/test_projection_kernel.py::test_the_memory_document_store_passes_the_conformance_kit
sandloom/core/tests/test_projection_kernel.py::test_the_conformance_kit_catches_a_store_without_the_version_guard
sandloom/core/tests/test_projection_kernel.py::test_the_memory_driver_advertises_and_passes_the_kit

C-OPS

Id Statement Enforced by
C-OPS-01 sandloom outbox list-dead | requeue [--database] [--channel] [--id|--all] lists dead-lettered outbox rows and returns them to pending (the admin API requeue is audited atomically and idempotent). sandloom/core/tests/test_outbox_relay_backoff.py::test_requeue_dead_returns_rows_to_pending
sandloom/core/tests/test_admin_operations.py::test_outbox_requeue_is_audited_atomically_and_idempotent
C-OPS-02 Delivered outbox rows older than OUTBOX_RETENTION_HOURS are purged by the relay on a time cadence and on demand by sandloom outbox purge. sandloom/core/tests/test_outbox_relay_batch.py::test_retention_purge_deletes_only_old_delivered_rows
sandloom/core/tests/test_outbox_relay_purge_cadence.py::test_purge_runs_on_a_time_cadence_not_a_tick_count
sandloom/core/tests/test_outbox_relay_purge_cadence.py::test_purge_stops_at_its_time_budget
gap (S): The sandloom outbox purge CLI entry point itself is not invoked by a test.
C-OPS-03 sandloom streams dlq-list | dlq-replay [--id] [--group] inspects and replays Streams dead letters, the replay targeted at the group that dead-lettered the entry. sandloom/core/tests/test_streams_split_identity.py::test_dlq_replay_reaches_only_the_failed_group
sandloom/core/tests/test_streams_split_identity.py::test_memory_dlq_replay_is_targeted_too
sandloom/core/tests/test_transport_redis.py::test_dlq_replay_republishes_and_clears
C-OPS-04 sandloom streams groups | consumers [--all] | release-group report group lag/pending, live consumers (flagging starved channel versions) and release a group. sandloom/core/tests/test_streams_split_identity.py::test_streams_cli_operations
sandloom/core/tests/test_streams_split_identity.py::test_inventory_lists_live_consumers_and_flags_starved_versions
sandloom/core/tests/test_streams_split_identity.py::test_group_lag_and_pending_are_reported
C-OPS-05 sandloom contracts snapshot [--write|--check] / sandloom contracts check --against exist for rolling-upgrade compatibility (owned by area D). sandloom/core/tests/test_contract_compat.py::test_committed_module_snapshots_match_registered_contracts
sandloom/core/tests/test_contract_compat.py::test_cli_check_against_snapshot
C-OPS-06 sandloom backup plan lists every database and the security Redis to back up without secrets; sandloom backup verify restores a dump into a scratch database and checks it against code heads. sandloom/core/tests/test_backup_cli.py::test_plan_covers_databases_and_security_redis_without_secrets
sandloom/core/tests/test_backup_cli.py::test_verify_passes_for_a_current_restore_and_drops_the_scratch_db
sandloom/core/tests/test_backup_cli.py::test_real_pg_dump_round_trip
C-OPS-07 sandloom backup reconcile-security and sandloom security audit-export re-derive blocks/revocations from the audit log and outbox (newest record wins, a tie stays blocked) and flag a too-short outbox retention as a coverage gap. sandloom/core/tests/test_security_recovery.py::test_audit_export_restores_blocks_a_restored_database_lost
sandloom/core/tests/test_security_recovery.py::test_newest_record_wins_and_a_tie_is_blocked
sandloom/core/tests/test_security_recovery.py::test_short_outbox_retention_is_flagged_as_a_coverage_gap
C-OPS-08 sandloom security revoke-sessions | force-password-reset (--all|--tenant ORG|--user ID) apply incident response in keyed, deduplicated batches through the owners' contracts; a forced reset refuses password login until reset. sandloom/core/tests/test_security_cli.py::test_apply_all_users_in_keyed_batches
sandloom/core/tests/test_security_cli.py::test_tenant_targets_page_through_membership
sandloom/core/tests/test_security_cli.py::test_missing_contract_is_an_explicit_error
sandloom_identity/tests/api/test_security_recovery_ops.py::test_forced_password_reset_refuses_password_login_until_reset
C-OPS-09 sandloom config dump [--json] [--only PREFIXES] prints effective settings with each value's source (env / dotenv / secrets_dir / default). sandloom/core/tests/test_ops_cli.py::test_effective_config_reports_sources
sandloom/core/tests/test_ops_cli.py::test_config_dump_cli
C-OPS-10 sandloom doctor is a read-only preflight (config, modules, infra reachability, migration heads, consumer lag, RLS, strict profile) exiting 0 ok / 1 warnings / 2 failures, rejecting unknown sections. sandloom/core/tests/test_ops_cli.py::test_doctor_exit_codes
sandloom/core/tests/test_ops_cli.py::test_doctor_reports_each_section_and_exit_code
sandloom/core/tests/test_ops_cli.py::test_doctor_reports_a_broken_composition_root
sandloom/core/tests/test_ops_cli.py::test_doctor_flags_strict_profile_problems
C-OPS-11 sandloom keys generate [--purpose rpc] produces usable JWT / RPC key pairs, optionally as SECRETS_DIR files, refusing to overwrite existing files and writing private keys with private permissions. sandloom/core/tests/test_asymmetric_jwt.py::test_keys_generate_prints_a_usable_pair
sandloom/core/tests/test_asymmetric_jwt.py::test_keys_generate_writes_secrets_dir_files
sandloom/core/tests/test_rpc_allowlist.py::test_keys_generate_rpc_validates
sandloom/core/tests/test_review_tooling_regressions.py::test_key_generation_refuses_existing_files_without_partial_rotation
C-OPS-12 Migration status compares each database's applied revisions with the code heads and reports unreachable databases without leaking secrets. sandloom/core/tests/test_ops_cli.py::test_migration_status_compares_applied_with_code_heads
sandloom/core/tests/test_ops_cli.py::test_migration_status_reports_unreachable_databases_without_secrets
C-OPS-13 Static CLIs (sandloom check, sandloom add) run with an empty environment without building Settings. sandloom/core/tests/test_static_cli_no_env.py::test_static_cli_runs_with_empty_environment
sandloom/core/tests/test_static_cli_no_env.py::test_sandloom_check_runs_against_a_manifest_with_empty_environment
sandloom/core/tests/test_static_cli_no_env.py::test_importing_static_submodules_does_not_build_settings
C-OPS-14 SECRETS_DIR supplies any setting as a file named after its field (Docker/K8s file-based secrets). sandloom/core/tests/test_audit_and_secrets.py::test_secrets_dir_reads_file

C-RISK

Id Statement Enforced by
C-RISK-01 SANDLOOM_ACCEPTED_RISKS (JSON list or comma list) names accepted risk ids and checks consult settings.accepts_risk(id). sandloom/core/tests/test_config_tiers.py::test_accepted_risks_parse_json_or_comma_list
sandloom/core/tests/test_password_hash_config.py::test_prod_accepts_presets_and_the_explicit_opt_out

C-DEPLOY

Id Statement Enforced by
C-DEPLOY-01 deploy/compose/monolith.yml runs every module in one app process plus worker and beat on one database, every process env validating, with an explicit BILLING_PROVIDER. sandloom/core/tests/test_deploy_profiles.py::test_every_process_env_validates
sandloom/core/tests/test_deploy_profiles.py::test_billing_provider_must_be_chosen
sandloom/core/tests/test_deploy_profiles.py::test_worker_and_beat_use_framework_entrypoint
C-DEPLOY-02 deploy/compose/microservices.yml runs one module per service behind an nginx gateway, each with its own database and migrate one-shot, wired by SERVICE_BINDINGS that are exactly the consumed-but-not-local contracts, with RPC allowlists derived from the bindings. sandloom/core/tests/test_deploy_profiles.py::test_microservices_db_per_service_and_bindings
sandloom/core/tests/test_deploy_profiles.py::test_microservices_bindings_are_exactly_the_consumed_contracts
sandloom/core/tests/test_deploy_profiles.py::test_gateway_routes_every_module_path_to_its_service
sandloom/core/tests/test_deploy_profiles.py::test_rpc_allowlist_is_derived_from_bindings
C-DEPLOY-03 The microservices stack boots end to end as real processes (split deployment smoke). tests/e2e/test_split_deployment.py::test_split_deployment_end_to_end
tests/e2e/test_split_deployment.py::test_required_mode_fails_instead_of_skipping
C-DEPLOY-04 Deploy profiles fit the Postgres connection budget, use one named image for every built service, a durable Redis bus, and (opt-in) a shared, durable idempotency Redis. sandloom/core/tests/test_deploy_profiles.py::test_connection_budget_fits_max_connections
sandloom/core/tests/test_deploy_profiles.py::test_one_named_image_for_every_built_service
sandloom/core/tests/test_deploy_profiles.py::test_redis_bus_is_persistent
sandloom/core/tests/test_deploy_profiles.py::test_idempotency_cache_opt_in_is_shared_and_durable
C-DEPLOY-05 The image serves via gunicorn + maintained uvicorn workers with a cgroup-aware default worker count (WEB_CONCURRENCY overrides) and LB-safe keep-alive. sandloom/core/tests/test_gunicorn_conf.py::test_gunicorn_worker_count_honours_cgroup_quota
sandloom/core/tests/test_gunicorn_conf.py::test_gunicorn_uses_maintained_uvicorn_worker_with_lb_safe_keepalive
C-DEPLOY-06 sandloom add deploy --service deploys a module whose required cross-module contracts are not bound yet (it reads the module's topology without the startup consumes preflight, which stays strict for applications), binds them to their providers' services, and gives a module with periodic tasks its -worker / -beat pair. sandloom/core/tests/test_scaffold_deploy.py::test_a_split_app_passes_the_framework_split_invariants
sandloom/core/tests/test_scaffold_deploy.py::test_a_module_with_periodic_tasks_gets_its_worker_and_one_beat
sandloom/core/tests/test_consumes_preflight.py::test_topology_read_records_a_missing_required_contract
C-DEPLOY-07 An sandloom new application splits into microservices with no code change. sandloom add deploy ... --service creates the app's microservices profile, nginx gateway and database init script from templates shipped in the package, then derives each service (its own database and migrate one-shot, RPC keys, worker and one beat), SERVICE_BINDINGS (plus the issuer's remote claims), RPC_ALLOWED_CALLS and the gateway routes from what the modules provide and consume. A repeated run changes nothing, a module is added to a service incrementally, hand edits outside the generated blocks are kept and an edit inside one is refused with a diff. The framework's own profile is the same generator's output. Route collection covers every route a service's app serves (round 3) - root-level routes (/ and /{slug} of module_router(prefix="")) get location = / and the catch-all location / (the internal surfaces then answering 404 explicitly), two services claiming the same route prefix, / or root-level parameter paths are refused with SANDLOOM-T014 naming them, never silently dropped, and a static catch-all left outside the generated routes block is refused. Each route prefix's locations stop at its segment boundary as Starlette matches (round 4) - location = /shop (to the service whose routes match the bare prefix) and location /shop/, never an unbounded location /shop, so /shopping stays with a root-level /{slug} service. sandloom/core/tests/test_scaffold_deploy.py::test_first_deploy_creates_the_profile_gateway_and_init_script
sandloom/core/tests/test_scaffold_deploy.py::test_a_split_app_passes_the_framework_split_invariants
sandloom/core/tests/test_scaffold_deploy.py::test_rerunning_is_idempotent
sandloom/core/tests/test_scaffold_deploy.py::test_adding_a_module_is_incremental_and_keeps_hand_edits
sandloom/core/tests/test_scaffold_deploy.py::test_a_hand_edit_inside_a_generated_block_is_refused_with_a_diff
sandloom/core/tests/test_scaffold_deploy.py::test_the_framework_profile_is_the_generators_output
sandloom/core/tests/test_scaffold_deploy.py::test_dev_env_covers_every_required_variable
sandloom/core/tests/test_scaffold_deploy.py::test_a_root_level_module_gets_the_root_and_the_catch_all
sandloom/core/tests/test_scaffold_deploy.py::test_two_services_claiming_root_level_paths_conflict
sandloom/core/tests/test_scaffold_deploy.py::test_a_hand_written_catch_all_outside_the_block_is_refused
sandloom/core/tests/test_scaffold_deploy.py::test_a_route_prefix_matches_only_its_own_segment
tests/e2e/test_app_split.py::test_an_sandloom_new_app_splits_into_microservices
C-DEPLOY-08 A microservices profile is checked against the split invariants the framework's own profile is pinned to (SANDLOOM-T014), in sandloom test's schema & deploy step for an application's profile too, and every HTTP service boots alone with exactly its environment (settings, startup preflight, composition root, every consumed contract bound) without network. The gateway check compares the gateway with every route each service's app serves (root-level / and /{slug} included), never a prefix-filtered list (round 3), and samples adversarial paths (round 4) - each path parameter also takes every literal segment a route has at that position, bare and suffixed - requiring the upstream to be the service whose app routes match the path (Starlette matching, the most literal route winning). sandloom/core/tests/test_deploy_profiles.py::test_the_profile_passes_the_split_invariants_apps_are_checked_with
sandloom/core/tests/test_deploy_profiles.py::test_the_split_invariants_report_what_breaks_them
sandloom/core/tests/test_deploy_profiles.py::test_sandloom_test_checks_the_split_profile_of_a_project
sandloom/core/tests/test_deploy_profiles.py::test_every_service_boots_alone_with_its_bindings
sandloom/core/tests/test_scaffold_deploy.py::test_the_checker_finds_a_root_route_the_gateway_drops
sandloom/core/tests/test_scaffold_deploy.py::test_the_checker_samples_paths_that_collide_with_another_prefix
C-DEPLOY-09 A service added to a running microservices stack gets its database although the Postgres volume exists (init-databases.sh runs only on an empty volume) - every generated migrate- one-shot runs sandloom migrate upgrade-all --apply --create-databases, which creates each hosting PostgreSQL database when missing (idempotent, with the citext and pg_trgm extensions), also in a profile generated before (the generated block carries the command). Race-safe (round 3) - provisioning is serialized per target name by an advisory lock on the maintenance database, and every caller ensures the extensions inside it whether or not it created the database, so a concurrent caller never returns (and migrates) before the database is complete. sandloom/core/tests/test_migrate_creates_database.py::test_a_missing_database_is_created_once_with_its_extensions
sandloom/core/tests/test_migrate_creates_database.py::test_a_concurrent_caller_never_sees_an_uninitialized_database
sandloom/core/tests/test_migrate_creates_database.py::test_an_existing_database_and_a_non_postgres_url_are_left_alone
sandloom/core/tests/test_migrate_creates_database.py::test_upgrade_all_takes_the_create_databases_flag
sandloom/core/tests/test_scaffold_deploy.py::test_a_service_added_later_creates_its_own_database
sandloom/core/tests/test_deploy_profiles.py::test_microservices_db_per_service_and_bindings
tests/e2e/test_app_split.py::test_an_sandloom_new_app_splits_into_microservices

C-OBS

Id Statement Enforced by
C-OBS-01 Logs are structured JSON with request/trace context ids, JSON automatically in prod (LOG_FORMAT=auto), and logging init is idempotent across create_app calls. sandloom/core/tests/test_observability.py::test_log_as_json_follows_environment
sandloom/core/tests/test_observability.py::test_json_formatter_emits_structured_line
sandloom/core/tests/test_observability.py::test_context_filter_injects_ids
sandloom/core/tests/test_observability.py::test_init_logging_is_idempotent_across_create_app_calls
C-OBS-02 Prometheus RED metrics are recorded per request, every safe_inc name resolves to a defined metric, and gauges declare a multiprocess mode so they render under gunicorn. sandloom/core/tests/test_observability.py::test_red_metrics_recorded_on_request
sandloom/core/tests/test_metrics_integrity.py::test_every_safe_inc_name_resolves_to_a_metric
sandloom/core/tests/test_operational_metrics.py::test_every_gauge_declares_a_live_multiprocess_mode
sandloom/core/tests/test_operational_metrics.py::test_new_gauges_render_from_the_multiprocess_directory
C-OBS-03 Outbox oldest-pending age (ignoring delivered/dead rows, refreshed even while the broker circuit is open) and Streams oldest-unacked age per group are exported. sandloom/core/tests/test_operational_metrics.py::test_outbox_oldest_pending_age_ignores_delivered_and_dead
sandloom/core/tests/test_operational_metrics.py::test_relay_tick_refreshes_age_while_the_broker_circuit_is_open
sandloom/core/tests/test_operational_metrics.py::test_streams_oldest_unacked_age_from_entry_ids
sandloom/core/tests/test_operational_metrics.py::test_heartbeat_sets_the_group_oldest_age_gauge
C-OBS-04 Pool, rate-limiter, revocation-check, RPC-client and consumer latency metrics are exported per mode/peer/outcome. sandloom/core/tests/test_operational_metrics.py::test_pool_sampler_reports_checked_out_idle_and_capacity
sandloom/core/tests/test_operational_metrics.py::test_rate_limiter_counts_rejections_and_cache_outages
sandloom/core/tests/test_operational_metrics.py::test_revocation_check_latency_by_mode
sandloom/core/tests/test_operational_metrics.py::test_rpc_client_latency_per_peer_and_outcome
C-OBS-05 OTLP tracing is configured from TRACING_URL (off without it); a span setup failure degrades to a no-op span and body exceptions propagate unchanged. sandloom/core/tests/test_tracing.py::test_tracing_stays_off_without_tracing_url
sandloom/core/tests/test_tracing.py::test_tracing_is_configured_from_settings
sandloom/core/tests/test_tracing.py::test_otlp_http_uses_http_exporter_and_flushes_once
sandloom/core/tests/test_tracing.py::test_span_setup_failure_degrades_to_a_noop_span
C-OBS-06 SENTRY_DSN enables error reporting (no-op when unset) with a before_send scrubber. sandloom/core/tests/test_ops_security_hardening.py::test_sentry_before_send_scrubs_event
gap (S): No test asserts Sentry init is a no-op without SENTRY_DSN / initialises with it.
C-OBS-07 GC tuning (GC_FREEZE_AFTER_STARTUP / GC_THRESHOLDS / GC_METRICS_ENABLED) freezes the startup heap and applies thresholds in gunicorn and Celery workers (entry points only, never create_app), with a pause histogram. sandloom/core/tests/test_gc_tuning.py::test_gc_settings_defaults
sandloom/core/tests/test_gc_tuning.py::test_post_worker_init_wraps_the_app_and_keeps_attributes
sandloom/core/tests/test_gc_tuning.py::test_celery_worker_init_freezes_in_the_parent
sandloom/core/tests/test_gc_tuning.py::test_gc_metrics_buffer_flushes_into_prometheus
C-OBS-08 The shipped alert/dashboard bundle references only existing metrics and labels, every alert has severity and a runbook entry matching docs/operate/operator-reference.md, and promtool rule tests pass. sandloom/core/tests/test_observability_bundle.py::test_every_referenced_metric_and_label_exists
sandloom/core/tests/test_observability_bundle.py::test_alerts_have_severity_and_runbook
sandloom/core/tests/test_observability_bundle.py::test_operations_alert_index_matches_rules
sandloom/core/tests/test_observability_bundle.py::test_promtool_rules_and_unit_tests

C-CONSOLE

Id Statement Enforced by
C-CONSOLE-01 The sandloom-admin wheel carries the build of the current console source and no application data, so an application serves it with neither Node nor a separate image. tests/architecture/test_admin_console_package.py::test_the_packaged_console_is_built_from_the_current_source
tests/architecture/test_admin_console_package.py::test_the_packaged_console_ships_no_application_data
C-CONSOLE-02 The console is off in production unless ADMIN_CONSOLE_ENABLED is set, forbids foreign code and framing, and never answers a missing asset with HTML. sandloom_admin/tests/test_console.py::test_production_keeps_it_off_unless_enabled
sandloom_admin/tests/test_console.py::test_the_entry_page_forbids_foreign_code
sandloom_admin/tests/test_console.py::test_a_missing_file_is_not_answered_with_html
C-CONSOLE-03 sandloom generate writes the console's manifest and refuses a declaration the application's API does not support; a console that cannot show the manifest says why instead of breaking. sandloom/core/tests/test_generated_admin_manifest.py::test_a_bad_declaration_is_a_problem_not_a_crash
sandloom_admin/tests/test_console.py::test_a_newer_manifest_version_is_refused
sandloom_admin/tests/test_console.py::test_a_widget_this_console_lacks_is_refused
C-CONSOLE-04 sandloom security grant-admin makes a registered user a platform administrator, verifies the address, keeps one assignment when repeated and writes a required audit row. sandloom_identity/tests/api/test_security_recovery_ops.py::test_appointing_the_first_admin_grants_verifies_and_audits
sandloom_identity/tests/api/test_security_recovery_ops.py::test_appointing_twice_keeps_one_assignment

C-TEST

Id Statement Enforced by
C-TEST-01 The sandloom.core.testing pytest plugin (-p sandloom.core.testing.plugin) loads .env.test before any settings import, honours a composition-root ini option and exposes the db/client/db_uow fixtures binding service effects to the test UoW. sandloom/core/tests/test_testing_plugin.py::test_plugin_exposes_db_uow_and_binds_service_effects
sandloom/core/tests/test_testing_plugin.py::test_env_test_is_loaded_before_any_conftest_imports_settings
sandloom/core/tests/test_testing_plugin.py::test_composition_root_ini_option
sandloom/core/tests/test_testing_plugin.py::test_missing_env_test_is_a_clear_usage_error
C-TEST-02 Under xdist each worker gets its own database _gw (created on first use) and Redis DB indexes offset by a configurable or controller-allocated stride. sandloom/core/tests/test_testing_workers.py::test_worker_gets_its_own_database_and_redis_dbs
sandloom/core/tests/test_testing_workers.py::test_stride_is_configurable
sandloom/core/tests/test_testing_workers.py::test_auto_stride_uses_the_controllers_allocation
sandloom/core/tests/test_testing_workers.py::test_ensure_database_creates_the_worker_database
C-TEST-03 SANDLOOM_TEST_DATABASE_URL / SANDLOOM_TEST_CACHE_URL / SANDLOOM_TEST_BROKER_URL win over .env.test on every load path (and are rewritten per xdist worker); reload_test_env reapplies the rewrite. sandloom/core/tests/test_testing_workers.py::test_explicit_overrides_win_over_env_test_on_every_load_path
sandloom/core/tests/test_testing_workers.py::test_overrides_are_rewritten_per_xdist_worker
sandloom/core/tests/test_testing_workers.py::test_reload_test_env_reapplies_the_worker_rewrite
C-TEST-05 tools/checks/test_resources.py list|prune manages shared test DBs / Redis indexes: prune is a dry run without --apply and never touches a connected or recently used resource. sandloom/core/tests/test_testing_resources.py::test_only_an_old_disconnected_marked_resource_is_stale
sandloom/core/tests/test_testing_resources.py::test_the_age_threshold_never_drops_below_a_day
sandloom/core/tests/test_testing_resources.py::test_scan_and_prune_a_stale_index_but_never_a_changed_one
sandloom/core/tests/test_testing_resources.py::test_a_database_is_stamped_on_claim_and_never_dropped_while_connected
C-TEST-06 Unit-test UoW helpers recording_uow() and bind_test_uow(session, database=) let tests capture required audits / effects without a real request. sandloom/core/tests/test_testing_harness.py::test_recording_uow_captures_required_audits
sandloom/core/tests/test_testing_harness.py::test_bind_test_uow_targets_the_named_database
sandloom/core/tests/test_testing_harness.py::test_required_audit_without_uow_raises
C-TEST-07 sandloom.sdk.testing.route_policy(route) reports what the framework applies to a route: auth (user / service / public), the public reason (deny-by-default is assertable from an app suite), the effect (read / write by method) and the risks resolved from their sources; assert_route_snapshot(app, *modules) fails unless each module's routes equal its committed routes.snapshot.json, naming the fix (SANDLOOM-T001), and fails on a missing snapshot. tests/sdk/test_sdk_testing_kit.py::test_route_policy_reads_auth_risk_and_the_public_reason
tests/sdk/test_sdk_testing_kit.py::test_assert_route_snapshot_pins_the_module_routes
C-TEST-08 published(E) lists the E events the code under test published with Event.publish(); a published() call without the testing plugin explains how to activate it; capture_effects also records a typed Event.publish() made from the listed modules. tests/sdk/test_sdk_events.py::test_publish_writes_the_outbox_row_and_is_recorded
tests/sdk/test_sdk_events.py::test_a_view_cannot_publish
tests/sdk/test_sdk_events.py::test_capture_effects_records_typed_publishes
C-TEST-09 fake_contract(REF | name, impl) serves the contract to Contract(REF) for the block, nested, and restores it. tests/sdk/test_sdk_testing_kit.py::test_fake_contract_nests_and_restores
C-TEST-10 deliver drives the real consumer runtime (schema preflight, the durable claim, the tenant binding), accepts an Event instance and keeps today's deliver_event parameters; as_user is make_user_headers. tests/sdk/test_sdk_testing_kit.py::test_deliver_drives_the_real_consumer_once
tests/sdk/test_sdk_testing_kit.py::test_deliver_keeps_the_channel_form
tests/sdk/test_sdk_testing_kit.py::test_as_user_is_make_user_headers
examples/projecthub/projecthub/modules/notifications/tests/test_notifications.py::test_a_redelivered_event_notifies_once
C-TEST-11 Under the client fixture a route's session is the test's shared one however the route obtains it: a DbSession route and a db() route behave identically (writes visible to the test and to later requests, a failed request not rolled back, on_commit callbacks after a successful request only); real_uow_client is the real-commit path for both. tests/integration/test_client_session_parity.py::test_a_dbsession_route_and_a_db_route_share_the_test_session
C-TEST-12 After every test the harness returns the pooled connections of the driver's default engines and of every database provider the active application built, so no later test (a new event loop) reuses a connection of a finished loop. sandloom/core/tests/test_release_pools.py::test_a_providers_own_engine_is_released_for_the_next_loop
C-TEST-13 sandloom test never aborts for want of parallel resources - -n auto is capped to the Redis database indexes the run holds or could claim (serially below two) with a warning, and the xdist steps run serially with a notice when pytest-xdist is not installed. sandloom/core/tests/test_testing_resources.py::test_auto_workers_fall_back_when_redis_indexes_are_short
sandloom/core/tests/test_testing_resources.py::test_affordable_workers_count_own_and_free_indexes_without_claiming
sandloom/core/tests/test_quickstart.py::test_sandloom_test_runs_serially_without_xdist
C-TEST-14 sandloom test runs a static schema & deploy step (no database, no Redis) before the suites: migration-chain coverage read from the revisions' syntax tree (SANDLOOM-T002, a constant table name counts), redundant indexes (SANDLOOM-T012), one beat per module with periodic tasks in every deploy profile (SANDLOOM-T010), and a warning for a TenantScoped key without tenant_id (SANDLOOM-T013). sandloom/core/tests/test_run_tests_cli.py::test_the_plan_of_the_framework_tree
sandloom/core/tests/test_model_indexes_migrated.py::test_tables_created_by_reads_the_chain_revisions
sandloom/core/tests/test_model_indexes_migrated.py::test_no_index_duplicates_a_unique_prefix
sandloom/core/tests/test_model_indexes_migrated.py::test_a_tenant_key_without_tenant_id_is_reported
sandloom/core/tests/test_deploy_profiles.py::test_a_module_with_periodic_tasks_and_no_beat_is_reported
C-TEST-15 fake_contract(REF, impl) also serves the contract to kernel code that resolves it through the active application (core.get_service - the billing and membership authz atoms), and fake_access_fact on the subscription facts drives the live SubscriptionActive / Subscribed(of=Account.org(...)) atoms, with or without billing loaded. sandloom/core/tests/test_testing_seams.py::test_fake_access_fact_drives_the_org_billing_atoms_without_billing
sandloom/core/tests/test_testing_seams.py::test_fake_contract_reaches_the_kernels_get_service

C-WORKER

Id Statement Enforced by
C-WORKER-01 celery -A sandloom.core.worker worker\|beat boots the composition root and imports .tasks (and setup_periodic_tasks) of every LOADED module only, skipping modules without tasks. sandloom/core/tests/test_worker.py::test_worker_registers_loaded_module_tasks_and_beat_schedule
sandloom/core/tests/test_worker.py::test_worker_without_identity_has_no_identity_tasks
sandloom/core/tests/test_worker.py::test_discover_skips_modules_without_tasks
sandloom/core/tests/test_worker.py::test_real_composition_root_resolves_driver_built_app
C-WORKER-02 The worker takes its Celery app from the taskqueue capability instance named by SANDLOOM_WORKER_TASKQUEUE, rejecting a non-Celery taskqueue and a composition root without a core; importing sandloom.core.worker has no side effects. sandloom/core/tests/test_worker.py::test_worker_honours_taskqueue_instance_env
sandloom/core/tests/test_worker.py::test_non_celery_taskqueue_is_rejected
sandloom/core/tests/test_worker.py::test_composition_root_must_expose_core
sandloom/core/tests/test_worker.py::test_import_has_no_side_effects
C-WORKER-03 Worker concurrency defaults from the CPU quota (capped, never the host CPU count) with WORKER_CONCURRENCY overrides validated. sandloom/core/tests/test_worker.py::test_default_concurrency_follows_cpu_quota_and_is_capped
sandloom/core/tests/test_worker.py::test_sizing_never_falls_back_to_host_cpu_count
sandloom/core/tests/test_worker.py::test_sizing_env_overrides
sandloom/core/tests/test_worker.py::test_sizing_rejects_bad_values
C-WORKER-04 run_async runs task coroutines on one persistent loop per worker process (fresh after fork, refused inside a running loop), draining the dispatcher and disposing engines on that loop at shutdown. sandloom/core/tests/test_async_runner.py::test_sequential_calls_share_one_persistent_loop
sandloom/core/tests/test_async_runner.py::test_inherited_loop_is_replaced_after_fork
sandloom/core/tests/test_async_runner.py::test_run_async_inside_a_running_loop_is_rejected
sandloom/core/tests/test_async_runner.py::test_dispatcher_is_drained_before_worker_returns_and_disposes_infra
C-WORKER-05 Celery tasks store no result by default and the worker prefetches one message (reliability defaults); enqueue never subscribes a result consumer. sandloom/core/tests/test_cache_batch_and_celery_defaults.py::test_worker_reliability_defaults
sandloom/core/tests/test_cache_batch_and_celery_defaults.py::test_driver_built_apps_ignore_results_by_default
sandloom/core/tests/test_cache_batch_and_celery_defaults.py::test_enqueue_never_subscribes_a_result_consumer
C-WORKER-06 The beat lease is scoped per service (replicas of one service share it, separate services do not) and wraps only this app's scheduler; the worker boot runs the tenant RLS role verification. sandloom/core/tests/test_beat_lease.py::test_lease_scope_separates_services_and_shares_replicas
sandloom/core/tests/test_beat_lease.py::test_beat_init_wraps_the_scheduler_of_this_app_only
sandloom/core/tests/test_beat_lease.py::test_two_leases_on_real_redis
sandloom/core/tests/test_worker.py::test_worker_boot_runs_tenant_rls_role_verification

C-SAGA

Id Statement Enforced by
C-SAGA-01 Modules declare linear, versioned sagas (Saga[InputT], step, local_step) with typed context and results, and start them with start_saga / read them with saga_status. sandloom/core/tests/test_sagas_definition.py::test_kinds_default_around_the_pivot
sandloom/core/tests/test_sagas_definition.py::test_context_key_and_typed_result
sandloom/core/tests/test_sagas_definition.py::test_a_matching_definition_builds
sandloom/core/tests/test_sagas_recovery.py::test_status_reads_the_handle
C-SAGA-02 Operators inspect and drive sagas with sandloom sagas list|show|retry|park|resolve (actionable default view, timeline with remediation) and sandloom doctor warns about parked instances; definitions appear in /diagnostics/core. sandloom/core/tests/test_sagas_cli.py::test_list_defaults_to_the_actionable_view
sandloom/core/tests/test_sagas_cli.py::test_show_prints_a_timeline_with_the_remediation
sandloom/core/tests/test_sagas_ops.py::test_list_and_show
sandloom/core/tests/test_sagas_channel.py::test_definitions_are_listed_in_core_diagnostics
C-SAGA-03 The saga_harness test kit drives the real protocol with fault injection and one-line compensation/resolve checks. sandloom/core/tests/test_sagas_recovery.py::test_resolve_one_liner_on_the_kernel_saga
sandloom/core/tests/test_sagas_recovery.py::test_compensation_idempotence_one_liner

C-DECL

Id Statement Enforced by
C-DECL-01 Event / on: typed construction, publish() through the outbox of the running unit (recorded for published()), meta before delivery raises EventMetaUnavailable. tests/sdk/test_sdk_events.py::test_construction_is_typed_and_strict
tests/sdk/test_sdk_events.py::test_meta_is_unavailable_before_delivery
tests/sdk/test_sdk_events.py::test_publish_writes_the_outbox_row_and_is_recorded
tests/sdk/test_sdk_events.py::test_abstract_base_shares_fields
C-DECL-02 contract / command / Contract: a stateless provider class becomes the L2 classmethod(contract_method(...)) form with every option (idempotency_key, idempotency_scope, database, planned); init, instance attributes, non-async or public non-method members and bases outside the package are refused (SANDLOOM-B018); Contract(REF) resolves when called (fakes, then the application resolver); typed saga steps name the command; @command(risk="money") is passed to contract_method(risk=) (the contract snapshot records it), and Contract(REF) resolves through the active core (sandloom.core.di.get_service) outside the test plugin. tests/sdk/test_sdk_contracts.py::test_provider_becomes_the_l2_form_with_every_option
tests/sdk/test_sdk_contracts.py::test_stateful_or_non_async_providers_are_refused
tests/sdk/test_sdk_contracts.py::test_command_needs_its_key_parameter
tests/sdk/test_sdk_contracts.py::test_caller_resolves_when_called_through_a_fake
tests/sdk/test_sdk_contracts.py::test_caller_uses_the_bound_resolver
tests/sdk/test_sdk_contracts.py::test_contract_needs_a_generated_reference
tests/sdk/test_sdk_contracts.py::test_typed_saga_step_names_the_command
tests/sdk/test_sdk_discovery.py::test_provider_bases_must_be_inside_the_package
tests/sdk/test_sdk_contracts.py::test_the_default_resolver_is_the_kernels_active_core
C-DECL-03 module_router: derived prefix and tags, no router-level auth; per route current_context and TenantScope(required=...) unless public() or a service principal; a route that binds the tenant itself gets no second binder; public() with an authenticator is refused (SANDLOOM-B008); tenant="derive" is required iff the module declares a TenantScoped model; a route with a service authenticator stamped by sandloom.sdk.web.authenticates(kind="service") gets nothing injected. tests/sdk/test_sdk_router.py::test_prefix_and_tags_are_derived_from_the_module
tests/sdk/test_sdk_router.py::test_authentication_and_tenant_are_injected_per_route
tests/sdk/test_sdk_router.py::test_optional_binding_does_not_require_a_tenant
tests/sdk/test_sdk_router.py::test_public_and_service_routes_get_nothing
tests/sdk/test_sdk_router.py::test_public_with_an_authenticator_is_refused
tests/sdk/test_sdk_router.py::test_tenant_binding_is_derived_from_tenant_scoped_models
tests/sdk/test_sdk_router.py::test_unauthenticated_requests_are_refused_and_public_ones_served
C-DECL-04 Projection (relational targets): key starts with tenant_id, one owning group per column, one version column per group, delta mappers only under the durable inbox, tombstones need their column (SANDLOOM-B012); full-state events converge under shuffles and duplicates, tombstones beat older upserts, deltas increment, views never return tombstones. tests/sdk/test_sdk_projections.py::test_invalid_projections_are_refused
tests/sdk/test_sdk_projections.py::test_the_l2_form_is_one_consumer_per_channel
tests/sdk/test_sdk_projections.py::test_full_state_converges_under_shuffles_and_duplicates
tests/sdk/test_sdk_projections.py::test_a_tombstone_beats_a_late_older_upsert
tests/sdk/test_sdk_projections.py::test_deltas_increment_and_versions_never_go_back
tests/sdk/test_sdk_projections.py::test_views_skip_tombstones_and_page
tests/sdk/test_sdk_projections.py::test_the_registered_handler_decodes_the_envelope
tests/laws/test_projection_laws.py::test_full_state_converges_to_the_max_version_event_per_group
tests/laws/test_projection_laws.py::test_a_tombstone_beats_any_late_older_upsert
tests/laws/test_projection_laws.py::test_a_column_has_exactly_one_owner_or_the_projection_is_refused
tests/laws/test_projection_laws.py::test_a_mapper_writing_a_column_its_group_does_not_own_is_refused
tests/laws/test_projection_laws.py::test_a_delta_mapper_is_accepted_only_under_the_durable_inbox
tests/laws/test_projection_laws.py::test_deltas_under_the_inbox_count_each_event_exactly_once
C-DECL-05 db() is the running unit's session wherever a unit runs: the request unit (lazy), open_uow() blocks, consumers, tasks and saga steps, and inside a contract method the contract's session (contract_session()), also under bind_contract_session. tests/integration/test_units_and_admission.py::test_db_in_a_block_is_that_units_session
sandloom/core/tests/test_db_in_contracts.py::test_db_in_a_bound_contract_is_the_bound_session

C-SDK

Id Statement Enforced by
C-SDK-01 sandloom docs answers a public name's home, tier, signature and documentation (and a module's names by tier) without a kernel read; every beginner code name defined in sandloom.sdk documents an example. tests/architecture/test_sdk_reference.py::test_sandloom_docs_answers_a_name
tests/architecture/test_sdk_reference.py::test_every_beginner_name_documents_an_example
C-SDK-02 Every public sandloom.sdk module starts with a generated reference block (beginner and advanced names, doc page) that is current (sandloom docs --write). tests/architecture/test_sdk_reference.py::test_every_public_module_carries_a_current_reference_block
tests/architecture/test_sdk_reference.py::test_a_block_lists_the_tiers_and_the_doc_page
C-SDK-04 sandloom docs describes what an app calls besides sandloom.sdk names, from the installed packages alone (the bundled sandloom.shared and the app's .shared): sandloom docs contracts / events list the generated stubs and exported schemas; sandloom docs (name or REF constant) prints the import and each method's signature and the provider's docstring; sandloom docs prints the fields, the producer's notes (versions, tenant field, ordering field) and a view to copy. Every method and class of a bundled contract stub states its semantics. tests/sdk/test_sdk_docs_wire.py::test_a_contract_prints_each_method_with_its_semantics
tests/sdk/test_sdk_docs_wire.py::test_an_event_prints_its_fields_and_the_producers_notes
tests/sdk/test_sdk_docs_wire.py::test_the_listings_name_every_stub_and_schema
tests/sdk/test_sdk_docs_wire.py::test_an_app_shared_package_is_read_too
tests/sdk/test_sdk_docs_wire.py::test_every_bundled_contract_method_states_its_semantics
C-SDK-05 The sandloom docs recipes with whole files (test-service-or-task, call-contract, projection-from-platform-events, event-v2, periodic-task) run as written in a generated app: their sandloom add / migrate new lines and # file: blocks in text order, then sandloom generate and sandloom test on the whole app and sandloom docs from the installed packages (tools/smoke/recipes_check.py, CI job app-recipes). tests/sdk/test_sdk_recipes.py::test_the_recipes_check_runs_every_verified_recipe
tests/sdk/test_sdk_recipes.py::test_recipe_parts_keep_the_text_order
tests/e2e/test_app_recipes.py::test_every_recipe_runs_in_a_generated_app
C-SDK-06 sandloom.sdk ships the id and timestamp idiom: new_id() returns an RFC 9562 UUIDv7 (version 7, RFC 4122 variant, the current Unix millisecond) that strictly increases within a process (a burst in one millisecond, a clock stepping back, a full counter) and orders by time across processes; CreatedAt / UpdatedAt map timestamptz NOT NULL columns defaulting to clock_timestamp() (UpdatedAt also on update), compose with TenantScoped, autogenerate as server_default=sa.text('clock_timestamp()'), and rows one transaction inserts list in creation order by created_at then id. sandloom docs describes them and the list-order recipe; the bundled modules' chains migrate every such column to the clock_timestamp() default. tests/sdk/test_sdk_columns.py::test_new_id_is_a_uuid7_stamped_with_the_current_millisecond
tests/sdk/test_sdk_columns.py::test_ids_of_a_burst_strictly_increase
tests/sdk/test_sdk_columns.py::test_the_fallback_orders_one_millisecond_and_a_clock_step_back
tests/sdk/test_sdk_columns.py::test_the_fallback_moves_to_the_next_millisecond_when_the_counter_is_full
tests/sdk/test_sdk_columns.py::test_ids_of_different_processes_order_by_time
tests/sdk/test_sdk_columns.py::test_the_timestamp_columns_default_to_clock_timestamp
tests/sdk/test_sdk_columns.py::test_the_columns_compose_with_tenant_scoped
tests/sdk/test_sdk_columns.py::test_alembic_autogenerate_renders_clock_timestamp
tests/sdk/test_sdk_columns.py::test_rows_of_one_transaction_list_in_creation_order
tests/sdk/test_sdk_columns.py::test_sandloom_docs_describes_the_idiom
tests/integration/test_migrations_per_chain.py::test_chains_reproduce_orm_schema_exactly
C-SDK-03 sandloom.sdk.tasks.shared_task is Celery's decorator typed with the task's signature (call, delay, apply_async, s), so a task module type-checks under strict mypy with no project override; cron() builds a beat schedule from a five-field expression. tests/sdk/test_sdk_generators.py::test_a_fully_generated_app_passes_the_static_gates

C-GEN

Id Statement Enforced by
C-GEN-01 sandloom add module writes new files inside the package only (model, schemas, services, events, module_router routes, a baseline migration with RLS rendered from the model, tests) and refuses an existing package or a bad name. tests/sdk/test_sdk_generators.py::test_add_module_writes_only_inside_the_package
C-GEN-02 sandloom add event appends a producer class to events.py, never rewriting it, and refuses a file that does not parse or lacks an import. tests/sdk/test_sdk_generators.py::test_add_event_appends_and_refuses_an_unparsable_file
C-GEN-03 sandloom add contract writes the provider in contracts.py and its DTO in the module's own schemas (the provider authors its DTOs; a second contract of the module joins the same schemas import), and writes the stub and the DTO copy at once exactly as sandloom generate renders them (so a saga or caller in the same scaffold boots); a generated stub imports ContractRef from its kernel home sandloom.core.descriptors.contract_refs (no sandloom.sdk import, no advanced marker), names the generated DTO copies, passes its Protocol, and is ruff format clean. tests/sdk/test_sdk_generators.py::test_add_contract_writes_the_generated_stub
tests/sdk/test_sdk_generated.py::test_a_generated_stub_is_ruff_formatted
C-GEN-04 sandloom add consumer writes a view from the producer schema (the tree or the exported file); without one it needs --field and marks the view unverified. tests/sdk/test_sdk_generators.py::test_add_consumer_takes_the_producer_schema_or_marks_unverified
C-GEN-05 sandloom add saga writes package files only; an existing target is kept and the output written as *.new. tests/sdk/test_sdk_generators.py::test_an_existing_target_is_never_overwritten
C-GEN-06 sandloom add projection writes the projection, its row model and a migration chained after the module chain head; a fully generated app (every generator) passes ruff, format, mypy --strict and sandloom check with the pending 1.0 checks, and every one of its tests passes. tests/sdk/test_sdk_generators.py::test_add_projection_chains_its_migration_after_the_head
tests/sdk/test_sdk_generators.py::test_a_fully_generated_app_passes_the_static_gates
tests/sdk/test_sdk_generators.py::test_a_fully_generated_app_passes_its_tests_up_to_the_h1_stubs
C-GEN-07 sandloom migrate new adds enable_tenant_rls / disable_tenant_rls (system_access for system_access models) for every TenantScoped table the autogenerated revision creates. tests/sdk/test_sdk_generators.py::test_migrate_new_adds_row_level_security_to_new_tenant_tables
C-GEN-08 sandloom add module wires the module where its project loads modules (the framework tree's LOCAL_MODULES, SANDLOOM_MODULES lists, the monolith profile; an sandloom new app discovers it) and, in the framework tree, gives its tests the kit's fixtures (client, published recording) through a one-line conftest. tests/sdk/test_sdk_generators.py::test_add_module_in_the_framework_tree_is_wired_and_gets_the_kit
C-GEN-09 sandloom add task [--periodic ""] writes an idempotent at-least-once task (acks_late, reject_on_worker_lost, run_async with an open_uow block) that passes the app's static gates, registers a periodic one in the module's setup_periodic_tasks, and wires the beat or prints the step. tests/sdk/test_sdk_generators.py::test_add_task_writes_an_idempotent_task_and_its_schedule
tests/sdk/test_sdk_generators.py::test_a_fully_generated_app_passes_the_static_gates
C-GEN-10 The quickstart sandloom new prints is the documented one (one source) and passes as printed in an empty directory - uv sync, sandloom add module, sandloom test, migrations, the app serving /health and an authenticated round trip, sandloom test --ci - with the dev services' ports configurable. sandloom/core/tests/test_quickstart.py::test_the_docs_quickstart_is_what_sandloom_new_prints
sandloom/core/tests/test_quickstart.py::test_dev_service_ports_are_configurable
sandloom/core/tests/test_quickstart.py::test_sandloom_new_writes_the_generated_files
sandloom/core/tests/test_quickstart.py::test_quickstart_end_to_end
C-GEN-11 sandloom add module generates a list route (GET /items) whose limit is capped with Query(le=...) over its TenantScoped table, newest first in a deterministic order (the model uses the framework's idiom - id defaults to new_id, a UUIDv7, and created_at is Mapped[CreatedAt], which defaults to clock_timestamp(), so rows created in one transaction do not tie - and the list orders by created_at then id), and a test that covers the order of rows created in one transaction, the page size, the cap (422) and another tenant's empty list. tests/sdk/test_sdk_generators.py::test_add_module_lists_items_capped_and_tenant_scoped
tests/sdk/test_sdk_generators.py::test_add_module_lists_newest_first_deterministically_in_one_transaction
tests/sdk/test_sdk_generators.py::test_a_fully_generated_app_passes_its_tests_up_to_the_h1_stubs
tests/sdk/test_sdk_columns.py::test_rows_of_one_transaction_list_in_creation_order
C-GEN-12 sandloom new --framework-source path writes editable sources, and an editable install copies no sandloom file (its .pth puts the live src/sandloom on sys.path), so the app always runs the checkout's current tree, never a cached build of the same version; the quickstart job fails when the installed framework is not the checkout. sandloom/core/tests/test_quickstart.py::test_framework_path_takes_the_repository_root
sandloom/core/tests/test_quickstart.py::test_an_editable_framework_install_is_the_live_tree
sandloom/core/tests/test_quickstart.py::test_quickstart_end_to_end
C-GEN-13 sandloom new writes the app's agent skills (sandloom-new-module, sandloom-quality-gates, sandloom-event-version-roll, sandloom-split-deploy), rewritten for the app's paths and commands, to .claude/skills and .agents/skills, and the app map names them in one line; sandloom add skills writes them into an existing app, keeping an edited file unless --force. sandloom/core/tests/test_project.py::test_generated_project_ships_the_app_skills
sandloom/core/tests/test_project.py::test_sandloom_add_skills_refreshes_an_existing_app
C-GEN-14 Example apps pass their own gate - every example app (examples/) and the tutorial app (docs/tutorial-app/) passes the gate an app developer runs, sandloom test --ci --full (generated files checked, schema & deploy, the full suite, ruff check without fixing, ruff format, mypy, sandloom check, contract and route snapshots, migration drift), in the CI examples job, which lists every such app; a generated contract stub is exactly what ruff format writes. tests/architecture/test_ci_local_runner.py::test_every_example_and_the_tutorial_app_run_their_own_gate_in_ci
sandloom/core/tests/test_run_tests_cli.py::test_ci_never_writes_so_ruff_check_never_fixes
tests/sdk/test_sdk_generated.py::test_a_rendered_stub_is_what_ruff_format_writes
tests/sdk/test_sdk_generated.py::test_a_generated_stub_is_ruff_formatted
C-GEN-15 sandloom generate [module ...] writes every generated file - module, route and contract snapshots, contract stubs, generated DTO copies, exported event schemas - after the compatibility checks (an incompatible change fails with its code and nothing is written; a weaker route policy is refused, pointing to sandloom routes snapshot --write); module names narrow it to those modules' files. sandloom test never writes a generated file, dev and --ci alike: a stale or hand-edited one fails, listed under "generated files are stale: run sandloom generate". Every sandloom add generator (but skills and deploy) runs sandloom generate last, and sandloom new writes them through it, so the files are current after scaffolding. sandloom/core/tests/test_run_tests_cli.py::test_sandloom_test_never_writes_a_generated_file
tests/sdk/test_sdk_generated.py::test_a_stale_file_asks_for_sandloom_generate_and_is_left_alone
tests/sdk/test_sdk_generated.py::test_generate_refuses_a_weakened_route_and_names_the_approval
tests/sdk/test_sdk_generated.py::test_an_incompatible_change_fails_before_anything_is_written
tests/sdk/test_sdk_generators.py::test_sandloom_add_runs_generate_at_the_end
tests/sdk/test_sdk_generators.py::test_add_contract_writes_the_generated_stub
sandloom/core/tests/test_quickstart.py::test_sandloom_new_writes_the_generated_files

Gaps

Id Size Missing
C-EVENT-01 S No test proves that publish_event_versions inside a UoW writes all versions as outbox rows atomically (rolled back together).
C-INFRA-05 S no test resolves a Capability(...) dependency in a request and asserts it yields the named provider (or the contract/feature error)
C-INFRA-10 S no test calls the /diagnostics/infra endpoint itself (only registry.describe(); the route appears only in the operation-class map)
C-MIGRATE-03 S Only flag plumbing is tested; no test runs upgrade-core --database against a second database.
C-OBS-06 S No test asserts Sentry init is a no-op without SENTRY_DSN / initialises with it.
C-OPS-02 S The sandloom outbox purge CLI entry point itself is not invoked by a test.
C-PROJ-03 S --tenant scoping of a rebuild is not exercised by a test.
C-PROJ-04 S No test (framework or examples) uses insert_only; a unit test proving INSERT seeds and UPDATE preserves the column is missing.
C-TENANT-02 S TenantScope(required=False) yielding None and leaving the context unbound, and custom header/claim_keys, are untested.
G-AUDIT-01 S No generic test that a plain record_audit followed by a UoW rollback leaves no audit_log row; only the block_user path proves it.
G-AUDIT-02 S The 'no audit store' branch of AuditUnavailableError (database driver without the audit feature) has no direct raising test (test_audit_store_skips_unsupported_backend only checks the store is absent).
G-COMPAT-03 S No test drives sandloom contracts check --against http://<provider> (live describe document over the internal token) or the sandloom contracts snapshot --write/--check CLI exit codes (only the underlying functions are tested).
G-COMPAT-07 S The startup log for a remote binding with no snapshot at all ("unchecked", CONTRACT_SNAPSHOT_PATHS absent) is not asserted.
G-CONFIG-05 S sandloom_config_override_active{setting,kind} metric export (app_factory) is not asserted by any test.
G-CONFIG-08 S AGENTS.md and accepted_risks error messages point at docs/reference/configuration-tiers.md, but that file does not exist in the repo (only tiers.render_markdown() in memory); nothing checks the committed page exists / is current.
G-EFFECT-03 S No test proves that a before_commit check registered inside a rolled-back savepoint is skipped (ADR §3 stage 1).
G-EVENT-05 S No test asserts the handler's DB writes are rolled back when the handler raises (the consumer UoW rollback itself); tests check redelivery with fake sessions only.
G-GUARD-01 S Tests call the order checker directly; no test boots create_app with a misordered route to prove the startup wiring.
G-INFRA-06 S no test asserts the preflight performs no I/O (opens no provider/connection), nor that a failed required preflight aborts create_app for a required module
G-MIGRATE-05 S Doc drift: AGENTS.md lists four core tables (outbox/audit/consumer_inbox/contract_command_log) but the chain (and the test) also own http_idempotency_log.
G-MIGRATE-06 S The prod refusal of ensure-outbox --apply without --force (cli/migrate.py) is untested.
G-MODULE-03 S no test proves a module importing sandloom.core.module_api._service_registry or sandloom.core.facade._state is flagged (only that the tree is clean)
G-OPS-12 S The prod --confirm-production requirement of sandloom security is untested (only sandloom backup's guard rails test it).
G-OUTBOX-04 S No test calls publish_event INSIDE a UoW with an invalid channel / schema-violating / oversized payload and asserts the call raises and no outbox_messages row exists; the in-UoW path (unit_of_work/effects.py preflight) is only covered indirectly.
G-PROFILE-05 S enforce_runtime is tested directly, but no test proves it is wired as a critical startup task of create_app's lifespan or of the Celery worker boot (removing either call site would stay green).
G-PROFILE-06 S The 'Cannot attest separate ... Redis processes' failure (deployment_identity raising) has no test.
G-REVOKE-06 S No test pins that password change/reset and user deactivation use the immediate (pre-commit) path specifically, rather than only that tokens end up revoked.
G-RISK-02 S Export of sandloom_accepted_risk_active{risk}=1 (app_factory) is never asserted; a test reading the metric after create_app in prod with a risk accepted is missing.
G-RISK-11 S No test exercises the unversioned_ddl_repair gate in sandloom/core/cli/migrate.py (only ensure-outbox dry-run / dev apply are tested).
G-RLS-05 S The BYPASSRLS-attribute branch (and membership in a BYPASSRLS/superuser role) has no direct test; only SUPERUSER, table ownership and system-role membership are exercised.
G-RLS-08 S 'No audit row, no commit' (a failing audit write aborts the system session's commit) and the structured start/end log record (op_id, outcome, duration) are not tested.
G-TENANT-14 S The 'token tenant does not match the configured tenant' 403 (token claim, no header) branch has no test.
G-TENANT-17 S The rule is only tested at the analyzer level (find_unsafe_tenant_sql); no test runs it through run_checks/sandloom check on a module fixture as the tenant-scoped-models rule is.
G-UOW-06 S Only the private resolver is unit-tested; no end-to-end test holds DbSession + Database("x") in one endpoint and asserts the event lands in the chosen database's outbox_messages (and is marked delivered there).
G-UOW-07 S Non-atomicity itself is a documented non-guarantee; no test pins that a failing second-database UoW leaves the first committed (i.e. that nobody quietly introduces a coupled commit).
G-BLOCK-04 M No generic check forces a custom (app-defined) issuer to call ensure_user_not_blocked; the rule is documentation-only for third-party issuers.
G-MODULE-17 M convention only: no sandloom check rule or test rejects a ForeignKey from one module's table to another module's table prefix

Removed

Id Statement Reason Approved Date
C-TEST-04 sandloom.core.testing.assert_operation_classes(app) fails an app with an unclassified or contradictorily classified route; sandloom new generates the check. superseded by C-TEST-07 (ADR-0009, accepted by the owner; spec §9.2): ADR-0009 removes assert_operation_classes outright; apps assert deny-by-default with sandloom.sdk.testing.route_policy(route) and pin their routes with assert_route_snapshot (C-TEST-07). owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-GUARD-03 create_app refuses (FinancialGuardError) an operation_class('F') route with a response-storing cache guard or a fail-open guard. superseded by G-SENS-05 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-IDEM-13 create_app refuses an operation_class("F") route whose idempotency guard is the response-storing cache form or fail-open (FinancialGuardError); durable=True or store_response=False + fail_closed=True are accepted. superseded by G-SENS-05 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-OPCLASS-01 Every API route of the composed monolith (kernel ops routes + identity + tenancy + billing) declares exactly one ADR-0003 operation class (P/R/RS/W/F/A) on the route or its router; an unclassified route fails CI, and apps run the same gate via sandloom.core.testing.assert_operation_classes(app). superseded by G-ROUTE-01 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-OPCLASS-02 create_app refuses (OperationClassError) a route with two different classes, an unknown class, a revocation_freshness budget outside class R, or a class RS/W/F/A route that is not always-strict. superseded by G-ROUTE-01 / G-ROUTE-04 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-REVOKE-13 revocation_freshness(max_staleness_s=N) requires 0 < N <= 5 (else ValueError) and may be declared only on GET/HEAD routes: create_app refuses a budget on any other method or on a router with a write route (RevocationPolicyError). superseded by G-REVOKE-21 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-REVOKE-14 A budgeted route answers from a per-process mark copy only while it is younger than the budget at DECISION time (re-checked after the live block read); a copy is never extended, and a copy expired or invalidated while a read was in flight is replaced by a live read of the mark. superseded by G-REVOKE-21 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-REVOKE-15 revoke_user_tokens drops the local copy and broadcasts on Redis pub/sub (sandloom:auth:revocations); other processes drop theirs, a lost broadcast delays a revocation on budgeted routes by at most the budget, only a drop of the same subject (or a wholesale clear on (re)subscribe) discards an in-flight read. superseded by G-REVOKE-21 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-REVOKE-16 A route marked always-strict (Depends(strict_revocation()) on it or its router, or a path under AUTH_REVOCATION_STRICT_PATH_PREFIXES) ignores a revocation_freshness budget (logged at startup) and always reads the mark live. superseded by G-REVOKE-21 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26
G-REVOKE-20 Per-request revocation cost: a strict route does one Redis round trip (MGET of revocation + block marks); a budgeted route with a fresh local copy does one GET of the block mark. superseded by G-REVOKE-21 (ADR-0009, accepted by the owner; spec §9.2) owner (ADR-0009 / ADR-0010 approval, 2026-10-25) 2026-10-26

Sources

Id Source
G-TENANT-01 docs/modules/database/multi-tenancy.md
docs/reference/security-model.md#Tenant isolation
G-TENANT-02 docs/modules/database/multi-tenancy.md
docs/reference/security-model.md#Tenant isolation
G-TENANT-03 docs/modules/database/multi-tenancy.md
docs/reference/security-model.md#Tenant isolation
G-TENANT-04 docs/modules/database/multi-tenancy.md
G-TENANT-05 docs/modules/database/multi-tenancy.md
G-TENANT-06 docs/modules/database/multi-tenancy.md
G-TENANT-07 docs/modules/database/multi-tenancy.md
docs/reference/security-model.md#Tenant isolation
G-TENANT-08 docs/modules/database/multi-tenancy.md
G-TENANT-09 docs/modules/database/multi-tenancy.md
G-TENANT-10 docs/modules/database/multi-tenancy.md
G-TENANT-11 docs/modules/database/multi-tenancy.md
G-TENANT-12 docs/modules/database/multi-tenancy.md
tests/fixtures/specs/public-api-1.0-spec.md#4.2 Route kinds
G-TENANT-13 docs/modules/database/multi-tenancy.md
G-TENANT-14 docs/modules/database/multi-tenancy.md
G-TENANT-15 docs/modules/database/multi-tenancy.md
G-TENANT-16 docs/modules/database/multi-tenancy.md
G-TENANT-17 docs/modules/database/multi-tenancy.md
G-TENANT-18 docs/modules/between/projections.md
G-RLS-01 docs/reference/security-model.md#What is guaranteed (tables with enable_tenant_rls, startup verification passed)
docs/reference/security-model.md#SQL paths available to business-module code
G-RLS-02 docs/reference/security-model.md#What is guaranteed (tables with enable_tenant_rls, startup verification passed)
G-RLS-03 docs/reference/security-model.md#What is guaranteed (tables with enable_tenant_rls, startup verification passed)
docs/modules/database/multi-tenancy.md
G-RLS-04 docs/reference/security-model.md#What is guaranteed (tables with enable_tenant_rls, startup verification passed)
G-RLS-05 docs/reference/security-model.md#What is guaranteed (tables with enable_tenant_rls, startup verification passed)
docs/modules/database/multi-tenancy.md
G-RLS-06 docs/modules/database/multi-tenancy.md
G-RLS-07 docs/modules/database/multi-tenancy.md
G-RLS-08 docs/reference/security-model.md#The system channel (privileged work)
docs/modules/database/multi-tenancy.md
G-RLS-09 docs/reference/security-model.md#The system channel (privileged work)
G-RLS-10 docs/reference/security-model.md#SQL paths available to business-module code
docs/modules/database/multi-tenancy.md
G-RLS-11 docs/reference/security-model.md#SQL paths available to business-module code
docs/reference/security-model.md#What is NOT guaranteed (residual risks)
G-RLS-12 docs/reference/security-model.md#SQL paths available to business-module code
G-RLS-13 docs/reference/security-model.md#What is guaranteed (tables with enable_tenant_rls, startup verification passed)
G-RLS-14 docs/reference/security-model.md#What is NOT guaranteed (residual risks)
docs/reference/security-model.md#Trust model of this Sandloom version (normative)
G-RLS-15 docs/reference/security-model.md#Tenant RLS threat model
G-PROFILE-01 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
docs/modules/database/multi-tenancy.md
G-PROFILE-02 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
G-PROFILE-03 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
docs/reference/security-model.md#The contract (what Sandloom guarantees, and under which conditions)
G-PROFILE-04 docs/reference/security-model.md#Production profile: the multi-tenant trust model as a contract
G-PROFILE-05 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
G-PROFILE-06 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
docs/modules/database/multi-tenancy.md
G-PROFILE-07 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
docs/reference/security-model.md#Security-critical vs best-effort cache
G-PROFILE-08 docs/reference/security-model.md#Security-critical vs best-effort cache
docs/modules/database/multi-tenancy.md
G-PROFILE-09 docs/reference/security-model.md#PRODUCTION_PROFILE=multi_tenant_strict
G-UOW-01 docs/modules/database/units.md
docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-UOW-02 docs/modules/database/units.md
G-UOW-03 docs/modules/database/units.md
docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-UOW-04 docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-UOW-05 docs/modules/database/units.md
docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-UOW-06 docs/modules/database/units.md
docs/modules/index.md#What you get by default, and what you declare
G-UOW-07 docs/modules/database/units.md
docs/adr/0005-effect-model.md#1. Vocabulary
G-UOW-08 docs/modules/database/units.md
G-UNIT-01 docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
tests/fixtures/specs/public-api-1.0-spec.md#4.1 Units
tests/fixtures/specs/public-api-1.0-spec.md#4.3 db() ownership
G-UNIT-02 docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
tests/fixtures/specs/public-api-1.0-spec.md#4.6 Operation-intent mode (billing checkout and portal)
G-UNIT-03 docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
tests/fixtures/specs/public-api-1.0-spec.md#4.4 Write admission: one writer per request
G-MSTORE-01 docs/adr/0010-public-api-1.0.md#5. One writer per request
tests/fixtures/specs/public-api-1.0-spec.md#6.1 Write store
G-MSTORE-02 docs/adr/0010-public-api-1.0.md#5. One writer per request
tests/fixtures/specs/public-api-1.0-spec.md#6.1 Write store
G-MSTORE-03 docs/adr/0010-public-api-1.0.md#5. One writer per request
tests/fixtures/specs/public-api-1.0-spec.md#4.4 Write admission: one writer per request
G-MSTORE-04 docs/adr/0010-public-api-1.0.md#5. One writer per request
tests/fixtures/specs/public-api-1.0-spec.md#4.4 Write admission: one writer per request
G-MSTORE-05 docs/adr/0010-public-api-1.0.md#5. One writer per request
tests/fixtures/specs/public-api-1.0-spec.md#6.1 Write store
G-MSTORE-06 docs/adr/0010-public-api-1.0.md#5. One writer per request
tests/fixtures/specs/public-api-1.0-spec.md#6.1 Write store
G-MSTORE-07 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
tests/fixtures/specs/public-api-1.0-spec.md#6.4 Projection convergence model
G-MSTORE-08 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
tests/fixtures/specs/public-api-1.0-spec.md#6.4 Projection convergence model
G-MSTORE-09 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
tests/fixtures/specs/public-api-1.0-spec.md#6.4 Projection convergence model
G-MSTORE-10 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
tests/fixtures/specs/public-api-1.0-spec.md#6.4 Projection convergence model
G-OUTBOX-01 docs/modules/database/units.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-02 docs/modules/database/units.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-03 docs/modules/database/units.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-04 docs/infrastructure/index.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-05 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-06 docs/modules/database/units.md
G-OUTBOX-07 docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-08 docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-OUTBOX-09 docs/modules/index.md#What you get by default, and what you declare
G-OUTBOX-10 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#4. Mechanism matrix
docs/operate/operator-reference.md#Events: outbox relay and dead letters
G-OUTBOX-11 docs/reference/cli.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-12 docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-13 docs/adr/0005-effect-model.md#4. Mechanism matrix
G-OUTBOX-14 docs/reference/cli.md
G-OUTBOX-15 docs/adr/0005-effect-model.md#1. Vocabulary
docs/modules/index.md#What you get by default, and what you declare
G-OUTBOX-16 docs/adr/0005-effect-model.md#6a. Declared requirements and guarantee windows (startup checks)
docs/reference/cli.md
G-EFFECT-01 docs/modules/database/units.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-EFFECT-02 docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-EFFECT-03 docs/modules/database/units.md
docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
G-EFFECT-04 docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
docs/adr/0005-effect-model.md#6. Rule for new mechanisms (complexity throttle)
G-EFFECT-05 docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
G-INBOX-01 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
G-INBOX-02 docs/modules/index.md#What a module may import
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-INBOX-03 docs/adr/0005-effect-model.md#4. Mechanism matrix
docs/modules/index.md#What you get by default, and what you declare
G-INBOX-04 docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
G-CMD-01 docs/modules/between/contracts.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
docs/adr/0005-effect-model.md#4a. Ambiguous COMMIT
G-CMD-02 docs/modules/between/contracts.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-CMD-03 docs/modules/between/contracts.md
G-CMD-04 docs/modules/between/contracts.md
G-CMD-05 docs/modules/between/contracts.md
docs/adr/0005-effect-model.md#Context
G-CMD-06 docs/modules/between/contracts.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-CMD-07 docs/modules/between/contracts.md
docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
G-CMD-08 docs/modules/between/contracts.md
G-CMD-09 docs/modules/between/contracts.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-IDEM-01 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Idempotency
G-IDEM-02 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-IDEM-03 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Idempotency
G-IDEM-04 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
G-IDEM-05 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Idempotency
G-IDEM-06 docs/reference/security-model.md#Idempotency
docs/modules/index.md#What you get by default, and what you declare
G-IDEM-07 docs/modules/index.md#What you get by default, and what you declare
G-IDEM-08 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Idempotency
G-IDEM-09 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-IDEM-10 docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
G-IDEM-11 docs/adr/0005-effect-model.md#4a. Ambiguous COMMIT
G-IDEM-12 docs/adr/0005-effect-model.md#2. The dedupe record — one primitive
docs/adr/0005-effect-model.md#Consequences
G-IDEM-14 docs/modules/index.md#What you get by default, and what you declare
G-IDEM-15 docs/reference/security-model.md#Idempotency
G-IDEM-16 docs/modules/index.md#What you get by default, and what you declare
docs/modules/database/multi-tenancy.md
G-IDEM-17 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-IDEM-18 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-TASK-01 docs/modules/background/tasks.md
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-TASK-02 docs/modules/background/tasks.md
G-TASK-03 docs/modules/background/tasks.md
docs/operate/operator-reference.md#Uncertain task enqueues
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-TASK-04 docs/modules/background/tasks.md
docs/modules/index.md#What you get by default, and what you declare
G-TASK-05 docs/modules/index.md#What you get by default, and what you declare
G-WINDOW-01 docs/adr/0005-effect-model.md#6a. Declared requirements and guarantee windows (startup checks)
G-WINDOW-02 docs/adr/0005-effect-model.md#6a. Declared requirements and guarantee windows (startup checks)
G-WINDOW-03 docs/adr/0005-effect-model.md#6a. Declared requirements and guarantee windows (startup checks)
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-EVENT-01 docs/modules/index.md#What a module may import
G-EVENT-02 docs/modules/index.md#What a module may import
G-EVENT-03 docs/modules/index.md#What a module may import
G-EVENT-04 docs/modules/index.md#What a module may import
G-EVENT-05 docs/modules/index.md#What a module may import
G-EVENT-06 docs/modules/index.md#What a module may import
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-EVENT-07 docs/modules/index.md#What a module may import
G-EVENT-08 docs/modules/index.md#What a module may import
G-EVENT-09 docs/modules/index.md#What a module may import
G-EVENT-10 docs/modules/index.md#What you get by default, and what you declare
docs/operate/operator-reference.md#Events: outbox relay and dead letters
G-EVENT-11 docs/modules/index.md#What you get by default, and what you declare
G-EVENT-12 docs/modules/index.md#What you get by default, and what you declare
G-EVENT-13 docs/modules/index.md#What you get by default, and what you declare
docs/modules/between/events.md#Changing an event
G-EVENT-14 docs/reference/cli.md
G-AUDIT-01 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Audit trail
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-AUDIT-02 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0005-effect-model.md#4. Mechanism matrix
G-AUTH-01 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-AUTH-02 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-AUTH-03 docs/reference/security-model.md#Authentication
docs/reference/cli.md
G-AUTH-04 docs/reference/security-model.md#Authentication
G-AUTH-05 docs/reference/security-model.md#Authentication
docs/reference/cli.md
G-AUTH-06 docs/reference/security-model.md#Authentication
docs/operate/topologies.md
G-AUTH-07 docs/reference/security-model.md#Authentication
G-AUTH-08 docs/reference/security-model.md#Authentication
G-AUTH-09 docs/reference/security-model.md#Authentication
G-AUTH-10 docs/reference/security-model.md#Secrets
docs/reference/cli.md
G-AUTH-11 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authorization
G-AUTH-12 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authorization
G-AUTH-13 docs/reference/security-model.md#Authorization
G-AUTH-14 docs/modules/index.md#What you get by default, and what you declare
G-AUTH-15 docs/reference/security-model.md#Authentication
docs/reference/cli.md
G-AUTH-16 docs/reference/security-model.md#Authentication
docs/reference/cli.md
G-AUTH-17 docs/reference/security-model.md#Authentication
docs/reference/cli.md
G-AUTH-18 docs/modules/standard/identity.md#Behaviour API clients must know
G-AUTH-19 docs/reference/security-model.md#Authentication
docs/reference/cli.md
docs/operate/topologies.md
G-REVOKE-01 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-02 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-03 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-04 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-05 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-06 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-07 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-08 docs/reference/security-model.md#Authentication
G-REVOKE-09 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-REVOKE-10 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
G-REVOKE-11 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Failure rules (fail-closed defaults)
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-REVOKE-12 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Decisions so far
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-REVOKE-17 docs/modules/database/units.md
docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Guarantees, precisely
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-REVOKE-18 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-REVOKE-19 docs/reference/security-model.md#Security-critical vs best-effort cache
docs/reference/cli.md
G-REVOKE-21 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
G-REVOKE-22 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-REVOKE-23 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-REVOKE-24 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-BLOCK-01 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-BLOCK-02 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
G-BLOCK-03 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
G-BLOCK-04 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Operation classes (normative)
G-BLOCK-05 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
G-BLOCK-06 docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
G-BLOCK-07 docs/adr/0009-route-policy-and-session-freshness.md#One subject-state record replaces the two marks
G-OPCLASS-03 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Platform routes → classes
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Decisions so far
docs/adr/0009-route-policy-and-session-freshness.md#The route snapshot
G-OPCLASS-04 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-OPCLASS-05 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
G-OPCLASS-06 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Guarantees, precisely
docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
G-OPCLASS-07 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Platform owner console routes
docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
G-ROUTE-01 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
docs/adr/0009-route-policy-and-session-freshness.md#Removed from the internal API before the initial public alpha
G-ROUTE-02 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-ROUTE-03 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-ROUTE-04 docs/adr/0009-route-policy-and-session-freshness.md#The route snapshot
docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
G-ROUTE-05 docs/adr/0009-route-policy-and-session-freshness.md#The route snapshot
G-SENS-01 docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
G-SENS-02 docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
G-SENS-03 docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
G-SENS-04 docs/adr/0009-route-policy-and-session-freshness.md#What risk triggers (applied by the framework)
G-SENS-05 docs/adr/0009-route-policy-and-session-freshness.md#What risk triggers (applied by the framework)
docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
G-SENS-06 docs/adr/0009-route-policy-and-session-freshness.md#What risk triggers (applied by the framework)
docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
G-AUTHZ-01 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Failure rules (fail-closed defaults)
docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
G-AUTHZ-02 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authorization
docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
G-AUTHZ-03 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0003-fewer-sync-rpc-membership-reads.md#What must stay strict (always a live RPC / local call)
docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
G-AUTHZ-04 docs/modules/index.md#What you get by default, and what you declare
G-AUTHZ-05 docs/modules/index.md#What you get by default, and what you declare
G-AUTHZ-06 docs/reference/security-model.md#Authorization
G-AUTHZ-07 docs/reference/security-model.md#Authorization
G-AUTHZ-08 docs/reference/security-model.md#Audit trail
G-AUTHZ-09 docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
tests/fixtures/specs/public-api-1.0-spec.md#7.2 of= (required, no default)
G-AUTHZ-10 docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
tests/fixtures/specs/public-api-1.0-spec.md#7.2 of= (required, no default)
G-AUTHZ-11 docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
G-AUTHZ-12 docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
G-GUARD-01 docs/modules/index.md#What you get by default, and what you declare
G-GUARD-02 docs/reference/security-model.md#Idempotency
G-HTTP-01 docs/reference/security-model.md#Transport & request hardening
docs/reference/cli.md
G-HTTP-02 docs/reference/cli.md
G-HTTP-03 docs/reference/security-model.md#Transport & request hardening
docs/reference/cli.md
G-HTTP-04 docs/reference/security-model.md#Transport & request hardening
docs/reference/cli.md
G-HTTP-05 docs/reference/security-model.md#Transport & request hardening
docs/reference/security-model.md#Secrets
G-HTTP-06 docs/reference/security-model.md#Transport & request hardening
docs/reference/cli.md
G-HTTP-07 docs/reference/security-model.md#Transport & request hardening
docs/reference/cli.md
G-HTTP-08 docs/reference/security-model.md#Transport & request hardening
G-HTTP-09 docs/reference/security-model.md#Transport & request hardening
G-HTTP-10 docs/reference/security-model.md#Transport & request hardening
docs/contributing/repository.md#Things that look wrong but are intentional
G-HTTP-11 docs/reference/security-model.md#Secrets
G-HTTP-12 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
G-BILLING-01 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-BILLING-02 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Guarantees, precisely
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-BILLING-03 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Guarantees, precisely
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Strict RPC is not zero staleness: the TOCTOU window
G-BILLING-04 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Intent lifecycle (state transitions)
G-BILLING-05 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Intent lifecycle (state transitions)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Open items (owner questions)
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-BILLING-06 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Intent ownership (application-level isolation)
G-BILLING-07 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
G-BILLING-08 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Open items (owner questions)
G-BILLING-09 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Intent reconciliation (#72)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Open items (owner questions)
G-BILLING-10 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
G-BILLING-11 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
docs/contributing/repository.md#Things that look wrong but are intentional
G-BILLING-12 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
G-BILLING-13 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
G-BILLING-14 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
G-BILLING-15 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Open items (owner questions)
G-BILLING-16 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Open items (owner questions)
G-BILLING-17 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Webhook matrix
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Open items (owner questions)
G-BILLING-18 docs/reference/cli.md
docs/reference/security-model.md#Secrets
docs/operate/topologies.md
G-BILLING-19 docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0003-fewer-sync-rpc-membership-reads.md#Gaps the classification exposed — resolved
docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
G-BILLING-20 docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
tests/fixtures/specs/public-api-1.0-spec.md#7.4 Account selection in billing itself
G-BILLING-21 docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
tests/fixtures/specs/public-api-1.0-spec.md#7.4 Account selection in billing itself
G-BILLING-22 docs/modules/standard/billing.md
G-CONTRACT-01 docs/modules/between/contracts.md
docs/modules/between/contracts.md#Local equals remote
G-CONTRACT-02 docs/modules/between/contracts.md
docs/modules/between/contracts.md#Local equals remote
G-CONTRACT-03 docs/modules/between/contracts.md
docs/modules/between/contracts.md#Local equals remote
G-CONTRACT-04 docs/modules/between/contracts.md
docs/modules/between/contracts.md#Local equals remote
G-CONTRACT-05 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Local == remote
G-CONTRACT-06 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Local == remote
G-CONTRACT-07 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Local == remote
G-CONTRACT-08 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Local == remote
G-CONTRACT-09 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Errors
G-CONTRACT-10 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Errors
G-CONTRACT-11 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Errors
G-CONTRACT-12 docs/modules/index.md#What you get by default, and what you declare
deploy/README.md#How the split works
G-CONTRACT-13 docs/modules/between/contracts.md
docs/modules/between/contracts.md#Local equals remote
G-CONTRACT-14 docs/modules/between/contracts.md
docs/modules/between/contracts.md#Local equals remote
G-CONTRACT-15 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Transport & performance
G-CONTRACT-16 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Local == remote
docs/operate/service-rpc.md#Transport & performance
G-CONTRACT-17 docs/operate/service-rpc.md#Local == remote
G-CONTRACT-18 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Request authentication and idempotency
G-CONTRACT-19 docs/modules/between/contracts.md
docs/adr/0010-public-api-1.0.md#Note (2026-10-28): contract DTOs authored in the provider, generated into the DTO package
G-COMPAT-01 docs/modules/between/contracts.md
deploy/README.md#Rolling upgrades: contract compatibility
G-COMPAT-02 docs/modules/between/contracts.md
deploy/README.md#Rolling upgrades: contract compatibility
G-COMPAT-03 docs/reference/cli.md
deploy/README.md#Rolling upgrades: contract compatibility
G-COMPAT-04 docs/modules/between/contracts.md
deploy/README.md#Rolling upgrades: contract compatibility
G-COMPAT-05 docs/modules/between/contracts.md
deploy/README.md#Rolling upgrades: contract compatibility
G-COMPAT-06 docs/modules/between/contracts.md
deploy/README.md#Rolling upgrades: contract compatibility
G-COMPAT-07 deploy/README.md#Rolling upgrades: contract compatibility
G-RPC-01 docs/adr/0004-rpc-pair-mac-request-authentication.md#Key schedule
docs/operate/service-rpc.md#Request authentication and idempotency
G-RPC-02 docs/adr/0004-rpc-pair-mac-request-authentication.md#Key schedule
docs/adr/0004-rpc-pair-mac-request-authentication.md#Threat model
G-RPC-03 docs/adr/0004-rpc-pair-mac-request-authentication.md#Request MAC
G-RPC-04 docs/adr/0004-rpc-pair-mac-request-authentication.md#Request MAC
docs/adr/0004-rpc-pair-mac-request-authentication.md#Errors
docs/operate/service-rpc.md#Request authentication and idempotency
G-RPC-05 docs/adr/0004-rpc-pair-mac-request-authentication.md#Request MAC
docs/adr/0004-rpc-pair-mac-request-authentication.md#Threat model
docs/reference/security-model.md#Service-to-service trust (internal RPC)
G-RPC-06 docs/adr/0004-rpc-pair-mac-request-authentication.md#Replay
docs/adr/0004-rpc-pair-mac-request-authentication.md#Third review (re-review of the KCI fix, 2026-10-21)
G-RPC-07 docs/adr/0004-rpc-pair-mac-request-authentication.md#Replay
docs/adr/0004-rpc-pair-mac-request-authentication.md#Security review
G-RPC-08 docs/adr/0004-rpc-pair-mac-request-authentication.md#Replay
docs/adr/0004-rpc-pair-mac-request-authentication.md#Second review (independent, 2026-10-21)
G-RPC-09 docs/adr/0004-rpc-pair-mac-request-authentication.md#Replay
docs/adr/0004-rpc-pair-mac-request-authentication.md#Second review (independent, 2026-10-21)
G-RPC-10 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Request authentication and idempotency
G-RPC-11 docs/modules/between/contracts.md
docs/adr/0004-rpc-pair-mac-request-authentication.md#Downgrade
docs/reference/security-model.md#Service-to-service trust (internal RPC)
G-RPC-12 docs/modules/between/contracts.md
docs/reference/security-model.md#Service-to-service trust (internal RPC)
G-RPC-13 docs/adr/0004-rpc-pair-mac-request-authentication.md#Key-compromise impersonation (KCI) and the command signature
docs/reference/security-model.md#Service-to-service trust (internal RPC)
G-RPC-14 docs/adr/0004-rpc-pair-mac-request-authentication.md#Key-compromise impersonation (KCI) and the command signature
docs/adr/0004-rpc-pair-mac-request-authentication.md#Key rotation
G-RPC-15 docs/modules/between/contracts.md
docs/adr/0004-rpc-pair-mac-request-authentication.md#Downgrade
docs/adr/0004-rpc-pair-mac-request-authentication.md#Amendment (2026-10-25)
G-RPC-16 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Provider endpoint
docs/adr/0004-rpc-pair-mac-request-authentication.md#Amendment (2026-10-25)
G-RPC-17 docs/adr/0004-rpc-pair-mac-request-authentication.md#Key rotation
docs/operate/service-rpc.md#Response binding
G-RPC-18 docs/modules/between/contracts.md
docs/adr/0004-rpc-pair-mac-request-authentication.md#Errors
docs/operate/service-rpc.md#Errors
G-RPC-19 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Response binding
docs/reference/security-model.md#Response binding — threat model
G-RPC-20 docs/adr/0004-rpc-pair-mac-request-authentication.md#Request MAC
docs/adr/0004-rpc-pair-mac-request-authentication.md#Security review
docs/reference/security-model.md#Response binding — threat model
G-RPC-21 docs/operate/service-rpc.md#Response binding
docs/reference/security-model.md#Response binding — threat model
docs/adr/0004-rpc-pair-mac-request-authentication.md#Amendment (2026-10-25)
G-RPC-22 docs/operate/service-rpc.md#Response binding
G-RPC-23 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Transport & performance
G-RPC-24 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Transport & performance
G-RPC-25 docs/modules/between/contracts.md
docs/adr/0004-rpc-pair-mac-request-authentication.md#Replay
docs/operate/service-rpc.md#Request authentication and idempotency
G-RPC-26 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Transport & performance
G-RPC-27 docs/operate/service-rpc.md#Transport & performance
G-RPC-28 docs/adr/0004-rpc-pair-mac-request-authentication.md#Downgrade
docs/adr/0004-rpc-pair-mac-request-authentication.md#Amendment (2026-10-25)
docs/operate/service-rpc.md#Provider endpoint
G-RPC-29 docs/adr/0004-rpc-pair-mac-request-authentication.md#Amendment (2026-10-25)
docs/reference/cli.md
G-RPC-30 docs/adr/0004-rpc-pair-mac-request-authentication.md#Key rotation
docs/reference/security-model.md#Service-to-service trust (internal RPC)
G-RPC-31 docs/adr/0004-rpc-pair-mac-request-authentication.md#Errors
docs/adr/0004-rpc-pair-mac-request-authentication.md#Security review
G-RPC-32 docs/operate/service-rpc.md#Request authentication and idempotency
docs/adr/0004-rpc-pair-mac-request-authentication.md#Second review (independent, 2026-10-21)
G-RPC-33 docs/reference/security-model.md#Service-to-service trust (internal RPC)
deploy/README.md#Probes (liveness vs readiness)
G-MODULE-01 docs/modules/index.md#What a module may import
docs/modules/index.md#How modules talk to each other
G-MODULE-02 docs/modules/index.md#What a module may import
docs/adr/0001-framework-distribution.md#Verification of current boundaries (from the codebase)
G-MODULE-03 docs/modules/index.md#What a module may import
G-MODULE-04 docs/modules/index.md#What a module may import
G-MODULE-05 docs/infrastructure/index.md
G-MODULE-06 docs/infrastructure/index.md
G-MODULE-07 docs/modules/database/units.md
G-MODULE-08 docs/modules/database/units.md
docs/modules/index.md#What you get by default, and what you declare
G-MODULE-09 docs/modules/index.md#What a module may import
G-MODULE-10 docs/modules/index.md#What you get by default, and what you declare
G-MODULE-11 docs/modules/index.md#What you get by default, and what you declare
docs/contributing/repository.md#Things that look wrong but are intentional
G-MODULE-12 docs/modules/index.md#What you get by default, and what you declare
G-MODULE-13 docs/reference/testing.md
docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
G-MODULE-14 docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
docs/modules/index.md#What you get by default, and what you declare
G-MODULE-15 docs/operate/topologies.md
G-MODULE-16 docs/modules/index.md#What you get by default, and what you declare
docs/contributing/repository.md#Repository map
G-MODULE-17 docs/modules/index.md#What you get by default, and what you declare
G-MODULE-18 docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
docs/modules/index.md#What you get by default, and what you declare
G-KERNEL-01 docs/adr/0007-kernel-layers.md#Decision
docs/adr/0007-kernel-layers.md#The map (top → bottom)
docs/modules/index.md#What a module may import
G-KERNEL-02 docs/adr/0007-kernel-layers.md#Decision
docs/adr/0007-kernel-layers.md#The one hook
docs/adr/0007-kernel-layers.md#Principles
G-KERNEL-03 docs/adr/0007-kernel-layers.md#Decision
docs/contributing/repository.md#Repository map
G-KERNEL-04 docs/adr/0007-kernel-layers.md#Decision
docs/adr/0007-kernel-layers.md#The default-registry fallback was dropped
G-KERNEL-05 docs/adr/0007-kernel-layers.md#Function-local imports
docs/modules/index.md#What a module may import
G-KERNEL-06 docs/adr/0007-kernel-layers.md#Function-local imports
docs/adr/0007-kernel-layers.md#Consequences
G-KERNEL-07 docs/infrastructure/index.md
docs/adr/0001-framework-distribution.md#Verification of current boundaries (from the codebase)
G-KERNEL-08 docs/adr/0007-kernel-layers.md#Principles
G-KERNEL-09 docs/modules/database/multi-tenancy.md
G-KERNEL-10 docs/modules/index.md#What a module may import
docs/adr/0001-framework-distribution.md#Verification of current boundaries (from the codebase)
G-KERNEL-11 docs/adr/0001-framework-distribution.md#3. Compatibility contract
docs/adr/0001-framework-distribution.md#Implementation status
docs/adr/0007-kernel-layers.md#What moved (no re-exports at the old paths)
G-KERNEL-12 docs/reference/testing.md
G-KERNEL-13 docs/modules/index.md#What you get by default, and what you declare
docs/adr/0001-framework-distribution.md#Boundary remediations required before the split
docs/adr/0001-framework-distribution.md#4. Assembly
G-INFRA-01 docs/infrastructure/index.md
docs/adr/0007-kernel-layers.md#Principles
G-INFRA-02 docs/adr/0007-kernel-layers.md#The default-registry fallback was dropped
docs/infrastructure/index.md
G-INFRA-03 docs/infrastructure/index.md
docs/infrastructure/index.md
G-INFRA-04 docs/infrastructure/index.md
G-INFRA-05 docs/infrastructure/index.md
G-INFRA-06 docs/infrastructure/index.md
G-INFRA-07 docs/infrastructure/index.md
G-INFRA-08 docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
docs/reference/testing.md
G-CONFIG-01 docs/reference/cli.md
docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
G-CONFIG-02 docs/reference/cli.md
docs/adr/0006-accepted-risks.md#Consequences
docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
G-CONFIG-03 docs/reference/cli.md
docs/adr/0006-accepted-risks.md#Decision
docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
docs/adr/0009-route-policy-and-session-freshness.md#Removed from the internal API before the initial public alpha
G-CONFIG-04 docs/reference/cli.md
docs/operate/operator-reference.md#Secrets management
G-CONFIG-05 docs/reference/cli.md
docs/operate/operator-reference.md#Preflight and effective configuration
G-CONFIG-06 docs/reference/cli.md
G-CONFIG-07 docs/contributing/repository.md#Repository map
G-CONFIG-08 docs/reference/cli.md
G-RISK-01 docs/adr/0006-accepted-risks.md#Decision
docs/reference/cli.md
G-RISK-02 docs/adr/0006-accepted-risks.md#Decision
docs/reference/cli.md
G-RISK-03 docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
docs/adr/0006-accepted-risks.md#Consequences
G-RISK-04 docs/reference/cli.md
docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
G-RISK-05 docs/adr/0006-accepted-risks.md#Decision
docs/reference/cli.md
G-RISK-06 docs/infrastructure/index.md
docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
G-RISK-07 docs/reference/cli.md
docs/contributing/repository.md#Things that look wrong but are intentional
G-RISK-08 docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
G-RISK-09 docs/reference/cli.md
docs/operate/operator-reference.md#Password hashing: work factor & capacity planning
G-RISK-10 docs/reference/cli.md
G-RISK-11 docs/reference/cli.md
docs/operate/operator-reference.md#Database migrations
G-RISK-12 docs/modules/database/units.md#Effects that wait for the commit, checks that run before it
docs/adr/0006-accepted-risks.md#Amendment (2026-10-25)
G-MIGRATE-01 docs/modules/index.md#What you get by default, and what you declare
docs/operate/operator-reference.md#Database migrations
G-MIGRATE-02 docs/modules/index.md#What you get by default, and what you declare
G-MIGRATE-03 docs/modules/index.md#What you get by default, and what you declare
G-MIGRATE-04 docs/reference/cli.md
docs/operate/operator-reference.md#Database migrations
G-MIGRATE-05 docs/contributing/repository.md#Repository map
docs/modules/index.md#What you get by default, and what you declare
G-MIGRATE-06 docs/reference/cli.md
docs/operate/operator-reference.md#Database migrations
G-MIGRATE-07 deploy/README.md#Runbook: moving a module's data out of the monolith database
G-MIGRATE-08 deploy/README.md#Runbook: moving a module's data out of the monolith database
G-MIGRATE-09 docs/modules/background/tasks.md
docs/reference/cli.md
G-MIGRATE-10 docs/operate/operator-reference.md#Database migrations
G-MIGRATE-11 docs/infrastructure/index.md
G-OPS-01 docs/modules/background/tasks.md
deploy/README.md#Background work (Celery worker + beat)
G-OPS-02 docs/contributing/repository.md#Things that look wrong but are intentional
deploy/README.md#Probes (liveness vs readiness)
G-OPS-03 docs/operate/topologies.md
deploy/README.md#Probes (liveness vs readiness)
G-OPS-04 docs/reference/cli.md
G-OPS-05 docs/operate/topologies.md
deploy/README.md#Background work (Celery worker + beat)
G-OPS-06 docs/reference/cli.md
docs/adr/0006-accepted-risks.md#Decision
deploy/README.md#Multiworker metrics and shutdown
G-OPS-07 docs/reference/testing.md
G-OPS-08 docs/reference/testing.md
G-OPS-09 docs/reference/testing.md
G-OPS-10 docs/reference/testing.md
docs/toolchain/testing.md#Test isolation
G-OPS-11 docs/reference/cli.md
docs/operate/operator-reference.md#Preflight and effective configuration
G-OPS-12 docs/reference/cli.md
docs/operate/operator-reference.md#Security incident runbook (database leak / credential leak / key compromise)
G-OPS-13 docs/operate/operator-reference.md#Observability
docs/operate/operator-reference.md#Data retention & PII
G-OPS-14 docs/operate/topologies.md
deploy/README.md#Config parity invariants
G-OPS-15 docs/operate/topologies.md
deploy/README.md#Gateway and client IPs (X-Forwarded-For)
G-OPS-16 docs/operate/topologies.md
deploy/README.md#Migrations (one-shot migrate services)
G-OPS-17 docs/modules/background/tasks.md
deploy/README.md#Background work (Celery worker + beat)
G-OPS-18 docs/operate/operator-reference.md#Backups & restore
G-OPS-19 docs/operate/operator-reference.md#Restoring: the order, and the traffic gate
docs/operate/operator-reference.md#Backups & restore
G-OPS-20 docs/operate/topologies.md
G-OPS-21 docs/reference/cli.md
deploy/README.md#Required modules
G-OPS-22 docs/operate/split-into-services.md
docs/reference/security-model.md
G-SAGA-01 docs/adr/0008-sagas.md#1. Definition (registration-time checks)
G-SAGA-02 docs/adr/0008-sagas.md#1. Definition (registration-time checks)
G-SAGA-03 docs/adr/0008-sagas.md#2. State: two core-chain tables in the owning module's database
G-SAGA-04 docs/adr/0008-sagas.md#3. Operation keys
G-SAGA-05 docs/adr/0008-sagas.md#4. Start: transactional with the business change
G-SAGA-06 docs/adr/0008-sagas.md#5. Driving: an outbox-fed consumer plus a timer sweep
G-SAGA-07 docs/adr/0008-sagas.md#6. The step protocol (one UoW per step)
G-SAGA-08 docs/adr/0008-sagas.md#5. Driving: an outbox-fed consumer plus a timer sweep
G-SAGA-09 docs/adr/0008-sagas.md#7. Recovery rules
G-SAGA-10 docs/adr/0008-sagas.md#7. Recovery rules
G-SAGA-11 docs/adr/0008-sagas.md#7. Recovery rules
G-SAGA-12 docs/adr/0008-sagas.md#8. Operations
G-SAGA-13 docs/adr/0008-sagas.md#9. Effect-model amendment (ADR-0005 §4, one new row; no new record)
G-SAGA-14 docs/adr/0008-sagas.md#11. Versioning in-flight instances
G-SAGA-15 docs/adr/0008-sagas.md#8. Operations
G-SDK-01 tests/fixtures/specs/public-api-1.0-spec.md#2.4 Import isolation
docs/adr/0010-public-api-1.0.md#1. One public namespace, sandloom.sdk, with a beginner tier
G-SDK-02 tests/fixtures/specs/public-api-1.0-spec.md#3. Three API levels
docs/adr/0010-public-api-1.0.md#2. Three API levels; level 1 is built only from level 2
G-SDK-03 tests/fixtures/specs/public-api-1.0-spec.md#3. Three API levels
docs/adr/0010-public-api-1.0.md#2. Three API levels; level 1 is built only from level 2
G-SDK-04 tests/fixtures/specs/public-api-1.0-spec.md#2.2 Modules
G-SDK-05 tests/fixtures/specs/public-api-1.0-spec.md#3. Three API levels
G-DECL-01 tests/fixtures/specs/public-api-1.0-spec.md#5.2 Typed events
G-DECL-02 tests/fixtures/specs/public-api-1.0-spec.md#5.4 Consumers
G-DECL-03 tests/fixtures/specs/public-api-1.0-spec.md#5.3 Typed contracts
G-DECL-04 tests/fixtures/specs/public-api-1.0-spec.md#5.3 Typed contracts
G-DECL-05 tests/fixtures/specs/public-api-1.0-spec.md#5.10 No function-local imports in modules
G-DECL-06 tests/fixtures/specs/public-api-1.0-spec.md#5.2 Typed events
G-DECL-07 docs/modules/between/contracts.md
docs/adr/0010-public-api-1.0.md#Note (2026-10-28): contract DTOs authored in the provider, generated into the DTO package
G-MODULE-19 tests/fixtures/specs/public-api-1.0-spec.md#5.7 The application manifest and discovery
G-MIGRATE-12 tests/fixtures/specs/public-api-1.0-spec.md#5.6 Generated migrations with RLS
tests/fixtures/specs/public-api-1.0-spec.md#5.8 sandloom test
G-AUTHZ-13 tests/fixtures/specs/public-api-1.0-spec.md#8.3 The authorization algebra is unchanged (Q4 revised twice)
tests/fixtures/specs/public-api-1.0-spec.md#8.2 Property-based (Hypothesis)
G-SENS-07 docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
tests/fixtures/specs/public-api-1.0-spec.md#8.2 Property-based (Hypothesis)
C-TENANT-01 docs/modules/database/multi-tenancy.md
C-TENANT-02 docs/modules/database/multi-tenancy.md
C-TENANT-03 docs/modules/database/multi-tenancy.md
docs/reference/security-model.md#Tenant isolation
C-TENANT-04 docs/modules/database/multi-tenancy.md
C-TENANT-05 docs/modules/database/multi-tenancy.md
docs/reference/security-model.md#Tenant RLS threat model
C-TENANT-06 docs/reference/security-model.md#The system channel (privileged work)
C-TENANT-07 docs/reference/security-model.md#Production profile: the multi-tenant trust model as a contract
C-TENANT-08 docs/reference/security-model.md#Security-critical vs best-effort cache
C-TENANT-09 docs/modules/database/multi-tenancy.md
C-EVENT-01 docs/modules/index.md#What you get by default, and what you declare
C-EVENT-02 docs/modules/index.md#What a module may import
C-EVENT-03 docs/modules/index.md#What you get by default, and what you declare
C-EFFECT-01 docs/modules/background/tasks.md
C-EFFECT-02 docs/modules/database/units.md
docs/adr/0005-effect-model.md#3. The commit pipeline — one stage list
C-AUTH-01 docs/reference/security-model.md#Authentication
C-AUTH-02 docs/reference/security-model.md#Authentication
C-AUTH-03 docs/reference/security-model.md#Authentication
docs/operate/topologies.md
C-AUTH-04 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authentication
C-AUTH-05 docs/modules/standard/identity.md#Behaviour API clients must know
C-AUTH-06 docs/modules/index.md#What you get by default, and what you declare
docs/reference/security-model.md#Authorization
docs/adr/0010-public-api-1.0.md#7. Explicit authorization subjects
C-AUTH-07 docs/modules/database/units.md
docs/reference/security-model.md#Authority at commit (admin and money effects)
docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
C-AUTH-08 docs/adr/0003-fewer-sync-rpc-membership-reads.md#Intent reconciliation (#72)
C-AUTH-09 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
tests/fixtures/specs/public-api-1.0-spec.md#2.3 New names: exact signatures
C-ROUTE-01 docs/adr/0009-route-policy-and-session-freshness.md#Defaults every route gets (nothing to declare)
C-ROUTE-02 docs/adr/0009-route-policy-and-session-freshness.md#Risk is declared at its source, once, by its owner
C-UNIT-01 docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
tests/fixtures/specs/public-api-1.0-spec.md#4.3 db() ownership
C-UNIT-02 docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
tests/fixtures/specs/public-api-1.0-spec.md#4.3 db() ownership
C-SPLIT-01 docs/operate/topologies.md
deploy/README.md#How the split works
C-SPLIT-02 docs/operate/topologies.md
docs/operate/service-rpc.md#Resolution order
C-SPLIT-03 docs/operate/topologies.md
C-SPLIT-04 docs/operate/topologies.md
deploy/README.md#Probes (liveness vs readiness)
C-SPLIT-05 deploy/README.md#How the split works
C-SPLIT-06 docs/operate/topologies.md
docs/operate/topologies.md
deploy/README.md#How the split works
C-SPLIT-07 docs/modules/index.md#What you get by default, and what you declare
docs/operate/topologies.md
C-RPC-01 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Transport & performance
C-RPC-02 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Transport & performance
C-RPC-03 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Request authentication and idempotency
C-RPC-04 docs/reference/security-model.md#Service-to-service trust (internal RPC)
C-RPC-05 docs/modules/between/contracts.md
docs/operate/service-rpc.md#Local == remote
C-INFRA-01 docs/infrastructure/index.md
C-INFRA-02 docs/infrastructure/index.md
docs/adr/0001-framework-distribution.md#2. Package graph (monorepo + uv workspace, N wheels, independent versions)
C-INFRA-03 docs/infrastructure/index.md
C-INFRA-04 docs/infrastructure/index.md
docs/modules/database/units.md
C-INFRA-05 docs/infrastructure/index.md
C-INFRA-06 docs/infrastructure/index.md
C-INFRA-07 docs/infrastructure/index.md
C-INFRA-08 docs/infrastructure/index.md
C-INFRA-09 docs/infrastructure/index.md
C-INFRA-10 docs/infrastructure/index.md
C-INFRA-11 docs/infrastructure/index.md
C-INFRA-12 docs/infrastructure/index.md
docs/adr/0010-public-api-1.0.md#5. One writer per request
C-MODULE-01 docs/modules/index.md#What you get by default, and what you declare
C-MODULE-02 docs/adr/0001-framework-distribution.md#5. Scaffold mechanics (sandloom new)
docs/modules/index.md#What you get by default, and what you declare
C-MODULE-03 docs/adr/0001-framework-distribution.md#1. Consumption model — three tiers
docs/adr/0001-framework-distribution.md#2. Package graph (monorepo + uv workspace, N wheels, independent versions)
docs/adr/0001-framework-distribution.md#4. Assembly
C-MODULE-04 docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
docs/adr/0002-framework-absorbs-module-boilerplate.md#Consequences
docs/modules/index.md#What you get by default, and what you declare
C-MODULE-05 docs/reference/cli.md
docs/modules/index.md#What a module may import
C-MODULE-06 docs/operate/topologies.md
docs/reference/cli.md
C-MODULE-07 docs/reference/cli.md
C-MODULE-08 docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
docs/adr/0002-framework-absorbs-module-boilerplate.md#Consequences
docs/reference/testing.md
C-MODULE-09 docs/adr/0002-framework-absorbs-module-boilerplate.md#Decision
docs/modules/index.md#What you get by default, and what you declare
C-MIGRATE-01 docs/reference/cli.md
deploy/README.md#Migrations (one-shot migrate services)
C-MIGRATE-02 docs/reference/cli.md
C-MIGRATE-03 docs/reference/cli.md
C-MIGRATE-04 docs/modules/index.md#What you get by default, and what you declare
C-MIGRATE-05 docs/reference/cli.md
C-MIGRATE-06 docs/reference/cli.md
deploy/README.md#Runbook: moving a module's data out of the monolith database
C-MIGRATE-07 docs/reference/cli.md
C-MIGRATE-08 docs/modules/index.md#What you get by default, and what you declare
C-PROJ-01 docs/modules/between/projections.md
C-PROJ-02 docs/modules/between/projections.md
C-PROJ-03 docs/modules/between/projections.md
docs/reference/cli.md
C-PROJ-04 docs/modules/between/projections.md
C-PROJ-05 docs/modules/between/projections.md
C-PROJ-06 docs/modules/between/projections.md
C-PROJ-07 docs/modules/between/projections.md
tests/fixtures/specs/public-api-1.0-spec.md#6.4 Projection convergence model
C-PROJ-08 docs/modules/between/projections.md
docs/modules/between/add-projection.md
C-MSTORE-01 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
C-MSTORE-02 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
C-MSTORE-03 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
tests/fixtures/specs/public-api-1.0-spec.md#6.3 Labels are derived from the mechanism
C-MSTORE-04 docs/adr/0010-public-api-1.0.md#6. Guarantees as a lattice, labels derived from mechanisms (amends ADR-0005)
docs/infrastructure/index.md
C-OPS-01 docs/reference/cli.md
docs/operate/operator-reference.md#Events: outbox relay and dead letters
C-OPS-02 docs/reference/cli.md
docs/operate/operator-reference.md#Events: outbox relay and dead letters
C-OPS-03 docs/reference/cli.md
docs/operate/operator-reference.md#Events: outbox relay and dead letters
C-OPS-04 docs/reference/cli.md
C-OPS-05 docs/reference/cli.md
deploy/README.md#Rolling upgrades: contract compatibility
C-OPS-06 docs/reference/cli.md
docs/operate/operator-reference.md#Backups & restore
C-OPS-07 docs/reference/cli.md
docs/operate/operator-reference.md#Restoring: the order, and the traffic gate
C-OPS-08 docs/reference/cli.md
docs/operate/operator-reference.md#Security incident runbook (database leak / credential leak / key compromise)
C-OPS-09 docs/reference/cli.md
docs/operate/operator-reference.md#Preflight and effective configuration
C-OPS-10 docs/reference/cli.md
docs/operate/operator-reference.md#Preflight and effective configuration
C-OPS-11 docs/reference/cli.md
docs/operate/operator-reference.md#Secrets management
C-OPS-12 docs/operate/operator-reference.md#Database migrations
docs/operate/operator-reference.md#Preflight and effective configuration
C-OPS-13 docs/reference/cli.md
C-OPS-14 docs/reference/cli.md
docs/operate/operator-reference.md#Secrets management
C-RISK-01 docs/adr/0006-accepted-risks.md#Decision
docs/reference/cli.md
C-DEPLOY-01 docs/operate/topologies.md
deploy/README.md#Profiles
C-DEPLOY-02 docs/operate/topologies.md
deploy/README.md#How the split works
C-DEPLOY-03 deploy/README.md#Smoke test (CI: microservices-smoke)
C-DEPLOY-04 deploy/README.md#Sizing
deploy/README.md#Separate idempotency Redis (opt-in)
C-DEPLOY-05 docs/operate/topologies.md
deploy/README.md#Sizing
C-OBS-01 docs/reference/cli.md
docs/operate/operator-reference.md#Observability
C-OBS-02 docs/operate/operator-reference.md#Operational metrics
deploy/README.md#Multiworker metrics and shutdown
C-OBS-03 docs/operate/operator-reference.md#Operational metrics
C-OBS-04 docs/operate/operator-reference.md#Operational metrics
C-OBS-05 docs/reference/cli.md
docs/operate/operator-reference.md#Observability
C-OBS-06 docs/reference/cli.md
docs/operate/operator-reference.md#Observability
C-OBS-07 docs/reference/cli.md
docs/operate/operator-reference.md#Garbage collector tuning
C-OBS-08 docs/operate/operator-reference.md#Alerts
C-CONSOLE-01 docs/admin-console/index.md
C-CONSOLE-02 docs/admin-console/deploy.md
C-CONSOLE-03 docs/admin-console/add-your-module.md
C-CONSOLE-04 docs/admin-console/index.md
C-TEST-01 docs/reference/testing.md
C-TEST-02 docs/reference/cli.md
docs/reference/testing.md
C-TEST-03 docs/reference/cli.md
C-TEST-05 docs/reference/cli.md
C-TEST-06 docs/reference/testing.md
C-WORKER-01 docs/modules/background/tasks.md
deploy/README.md#Background work (Celery worker + beat)
C-WORKER-02 docs/modules/background/tasks.md
C-WORKER-03 docs/operate/topologies.md
deploy/README.md#Sizing
C-WORKER-04 docs/modules/background/tasks.md
C-WORKER-05 docs/modules/index.md#What you get by default, and what you declare
C-WORKER-06 docs/modules/background/tasks.md
deploy/README.md#Background work (Celery worker + beat)
C-SAGA-01 docs/adr/0008-sagas.md#1. Definition (registration-time checks)
C-SAGA-02 docs/adr/0008-sagas.md#8. Operations
C-SAGA-03 docs/adr/0008-sagas.md#13. Testing
C-DECL-01 tests/fixtures/specs/public-api-1.0-spec.md#2.3 New names: exact signatures
C-DECL-02 tests/fixtures/specs/public-api-1.0-spec.md#5.3 Typed contracts
C-DECL-03 tests/fixtures/specs/public-api-1.0-spec.md#5.1 Model E and the module router
C-DECL-04 tests/fixtures/specs/public-api-1.0-spec.md#6.4 Projection convergence model
C-DECL-05 docs/adr/0010-public-api-1.0.md#4. Transaction and replay lifecycle
tests/fixtures/specs/public-api-1.0-spec.md#5.5 db()
C-SDK-01 tests/fixtures/specs/public-api-1.0-spec.md#2.5 Zero kernel reads
C-SDK-02 tests/fixtures/specs/public-api-1.0-spec.md#2.5 Zero kernel reads
C-GEN-01 tests/fixtures/specs/public-api-1.0-spec.md#5.9 Generators
C-GEN-02 tests/fixtures/specs/public-api-1.0-spec.md#5.9 Generators
C-GEN-03 tests/fixtures/specs/public-api-1.0-spec.md#5.9 Generators
C-GEN-04 tests/fixtures/specs/public-api-1.0-spec.md#5.9 Generators
C-GEN-05 tests/fixtures/specs/public-api-1.0-spec.md#5.9 Generators
C-GEN-06 tests/fixtures/specs/public-api-1.0-spec.md#5.9 Generators
C-GEN-07 tests/fixtures/specs/public-api-1.0-spec.md#5.6 Generated migrations with RLS
C-TEST-07 tests/fixtures/specs/public-api-1.0-spec.md#2.3 New names: exact signatures
docs/adr/0009-route-policy-and-session-freshness.md#The route snapshot
C-TEST-08 tests/fixtures/specs/public-api-1.0-spec.md#2.3 New names: exact signatures
C-TEST-09 tests/fixtures/specs/public-api-1.0-spec.md#2.3 New names: exact signatures
C-TEST-10 tests/fixtures/specs/public-api-1.0-spec.md#2.3 New names: exact signatures
docs/reference/testing.md
C-TEST-11 docs/reference/testing.md
C-TEST-12 docs/reference/testing.md
C-ROUTE-03 docs/adr/0009-route-policy-and-session-freshness.md#Checks (deterministic; each error names its fix)
tests/fixtures/specs/public-api-1.0-spec.md#5.1 Model E and the module router
C-DEPLOY-06 deploy/README.md
docs/modules/background/tasks.md
C-DEPLOY-07 docs/operate/split-into-services.md
deploy/README.md
C-DEPLOY-08 docs/operate/split-into-services.md
deploy/AGENTS.md
C-DEPLOY-09 docs/operate/split-into-services.md
deploy/README.md
C-TEST-13 docs/reference/testing.md
C-TEST-14 docs/reference/error-codes.md
C-TEST-15 docs/reference/testing.md
C-TENANT-10 docs/modules/database/multi-tenancy.md
C-GEN-08 docs/modules/create.md
C-GEN-09 docs/modules/background/tasks.md
C-GEN-10 docs/getting-started/index.md
C-GEN-11 docs/modules/create.md
docs/modules/database/conventions.md#Timestamps, ids and list order
C-GEN-12 docs/getting-started/index.md
C-SDK-04 tests/fixtures/specs/public-api-1.0-spec.md#2.5 Zero kernel reads
C-SDK-05 tests/fixtures/specs/public-api-1.0-spec.md#2.5 Zero kernel reads
C-SDK-06 docs/modules/database/conventions.md#Timestamps, ids and list order
C-GEN-13 docs/getting-started/index.md
C-GEN-14 examples/README.md
C-GEN-15 docs/reference/cli.md
C-MODULE-10 docs/modules/http/authorization.md
C-MODULE-11 docs/reference/testing.md
C-MODULE-12 docs/modules/index.md
docs/reference/error-codes.md#sandloom-b020
C-MODULE-13 packages/README.md
docs/adr/0001-framework-distribution.md#2. Package graph (monorepo + uv workspace, N wheels, independent versions)
C-SDK-03 docs/modules/background/tasks.md
C-MIGRATE-09 docs/modules/database/write-migration.md
C-PROJ-09 docs/modules/between/projections.md
C-EVENT-04 docs/modules/standard/tenancy.md
docs/modules/standard/billing.md