interlock-escrow 1.4.2__tar.gz → 1.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/CHANGELOG.md +32 -0
- interlock_escrow-1.4.3/PKG-INFO +386 -0
- interlock_escrow-1.4.3/README.md +353 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/demo.py +8 -8
- interlock_escrow-1.4.3/docs/GUIDE.md +927 -0
- interlock_escrow-1.4.2/README.md → interlock_escrow-1.4.3/docs/SYSTEMS.md +6 -1206
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/pyproject.toml +9 -2
- interlock_escrow-1.4.3/scripts/overhead.py +636 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/__init__.py +1 -1
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_readme_snippets.py +32 -11
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/uv.lock +1 -1
- interlock_escrow-1.4.2/PKG-INFO +0 -2563
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/.github/workflows/ci.yml +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/.github/workflows/release.yml +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/.gitignore +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/.python-version +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/LICENSE +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/SECURITY.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/demo/customer_acceptance_suite.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/CUSTOMER_ACCEPTANCE_MATRIX.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC3_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC4_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC5_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC6_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC7_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC8_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/EPIC9_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/ESCROW_SPEC.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/docs/OUTBOX_DESIGN.md +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/scripts/kind-setup.sh +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/scripts/live_gauntlet.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/scripts/live_stress_test.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/scripts/metrics_overhead.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/scripts/security_audit.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/adapters.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/adjudication.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/anchor.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/attestations.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/builder.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/cascade.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/chain.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/checker_packs.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/cli.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/cluster.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/compaction.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/config.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/control.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/daemon.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/deliveries.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/engine.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/exceptions.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/extension.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/feedback.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/fhir.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/git_substrate.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/inbox.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/inbox_sql.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/inbox_store.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/invariants.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/keys.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/kubernetes.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/mcp.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/meters.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/operators.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/outbound.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/outbound_checks.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/outbox_sql.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/outbox_store.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/planbound.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/postgres.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/predicted.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/py.typed +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/receipts.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/reconcile.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/records.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/recovery.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/relay.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/repair.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/runtime.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sampling.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sendgrid.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sensors.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/settlement.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/signers.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sim/__init__.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sim/fhir.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sim/kubernetes.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sim/terraform.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/sqlite_outbox.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/stripe.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/substrate.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/supervisor.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/telemetry.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/terraform.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/trace.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/types.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/vacuum.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/windows.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/src/interlock/wiring.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/__init__.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/checker_digests.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/children.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/conftest.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/crash_child.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/daemon_app.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/fakekms.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/fakepg.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/fakesendgrid.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/fakesink.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/fakestripe.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/inbox_child.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/inbox_env.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/operator_child.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/outbox_env.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/outbox_v2.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/outbox_v3.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/outbox_v4.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/pgbouncer/pgbouncer.ini +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/pgbouncer/userlist.txt +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/plans.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/real_systems.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/relay_child.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/schemas.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/settle_child.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/settling.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/sqlite_outbox_v3.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/sqlite_outbox_v4.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/sqlite_stage_child.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_acceptance_suite.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_agreement.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_anchor.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_attestations.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_cascades.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_checker_packs.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_checker_records.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_cluster.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_cluster_daemon.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_config.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_config_fhir.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_config_plan_bound.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_core_guarantees.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_crash_consistency.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_daemon.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_deliveries.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_engine.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_ergonomics.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_extension.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_fact_agreement.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_feedback.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_fhir.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_git_substrate.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_horizontal.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_inbox.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_inbox_cli.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_inbox_crash.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_inbox_engine.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_inbox_vendors.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_instrumentation.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_keys.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_kubernetes.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_live_sinks.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_metrics.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_operator_crash.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_operators.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_outbound.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_outbound_checks.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_outbox_postgres.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_pg_upgrade.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_planbound.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_pool.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_postgres.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_real_fhir.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_real_kubernetes.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_real_tofu.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_reconcile.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_records.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_recovery.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_relay.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_relay_crash.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_repair.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_runtime.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_sampling.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_security_audit.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_sendgrid.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_sensors.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_settlement.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_settlement_crash.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_shared_transaction.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_signers.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_soak.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_sqlite_outbox.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_sqlite_stage_crash.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_stripe.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_supervisor.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_takeover.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_tamper_evidence.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_terraform.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_trace.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_typed_config.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_upgrade_v5.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_vacuum.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_vacuum_crash.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_vacuum_inbox.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/test_windows.py +0 -0
- {interlock_escrow-1.4.2 → interlock_escrow-1.4.3}/tests/vacuum_child.py +0 -0
|
@@ -9,6 +9,38 @@ Releases before 0.1.2 are described by their tags and commit history.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [1.4.3] - 2026-11-06
|
|
13
|
+
|
|
14
|
+
Documentation, a benchmark and package metadata. No change to the library, and nothing to
|
|
15
|
+
do to upgrade.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`scripts/overhead.py`: what a write costs through Interlock,** on SQLite,
|
|
20
|
+
PostgreSQL, Git, Kubernetes and FHIR (on the loopback servers in `interlock.sim`) and an
|
|
21
|
+
outbound request through the outbox and the relay. Each system is timed directly and
|
|
22
|
+
through the whole path (staged, measured, checked, committed, chained and receipted) in
|
|
23
|
+
alternating rounds. It counts what each write costs in requests, connections, statements
|
|
24
|
+
or git processes, so a reader can add their own network's round trip. The README's table
|
|
25
|
+
replaces "staging roughly doubles write-path round trips".
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **The README is the front door.** It opens with how AgentGov, Interlock and Fiduciary fit
|
|
30
|
+
together, and links to the live console. It keeps the problem, what Interlock measures
|
|
31
|
+
(now with the four measurement classes T, P, V and R in one table), the lifecycle, the
|
|
32
|
+
quickstart, what a write costs, and install. It is down from 2,535 lines to about 350.
|
|
33
|
+
- **The rest moved, verbatim,** to `docs/GUIDE.md` (the engine) and `docs/SYSTEMS.md` (each
|
|
34
|
+
system, the outbox and the daemon). `test_readme_snippets` now runs every Python block in
|
|
35
|
+
all three files, all 25 of them, as it ran the README's before.
|
|
36
|
+
- **`demo.py` names its five steps "Scenario 1" to "Scenario 5",** not "ACT", as the rest of
|
|
37
|
+
the stack does since 1.4.2.
|
|
38
|
+
- **The development status is "4 - Beta",** not "5 - Production/Stable": a full test suite
|
|
39
|
+
and CI against real PostgreSQL, Kubernetes, OpenTofu and FHIR servers, but no external
|
|
40
|
+
production deployment yet.
|
|
41
|
+
- **The PyPI page links to** the live console, the guide, the repository, the issue tracker
|
|
42
|
+
and the changelog. 1.4.2 and earlier linked to nothing.
|
|
43
|
+
|
|
12
44
|
## [1.4.2] - 2026-11-05
|
|
13
45
|
|
|
14
46
|
What an agent is told when a plan is refused, in plainer sentences, and a
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: interlock-escrow
|
|
3
|
+
Version: 1.4.3
|
|
4
|
+
Summary: A reference monitor for agent side effects: stage, measure, adjudicate, commit.
|
|
5
|
+
Project-URL: Homepage, https://crimsondevil0929.github.io/fiduciary/
|
|
6
|
+
Project-URL: Documentation, https://github.com/crimsondevil0929/interlock/blob/main/docs/GUIDE.md
|
|
7
|
+
Project-URL: Repository, https://github.com/crimsondevil0929/interlock
|
|
8
|
+
Project-URL: Issues, https://github.com/crimsondevil0929/interlock/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/crimsondevil0929/interlock/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Edward Gu <ygu0929@uchicago.edu>
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agents,audit,escrow,llm,prompt-injection,reference-monitor,zero-trust
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: interlock-agentgov>=1.3.0
|
|
24
|
+
Provides-Extra: kms-aws
|
|
25
|
+
Requires-Dist: interlock-agentgov[kms-aws]>=1.3.0; extra == 'kms-aws'
|
|
26
|
+
Provides-Extra: otel
|
|
27
|
+
Requires-Dist: interlock-agentgov[otel]>=1.3.0; extra == 'otel'
|
|
28
|
+
Provides-Extra: postgres
|
|
29
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == 'postgres'
|
|
30
|
+
Provides-Extra: sign
|
|
31
|
+
Requires-Dist: interlock-agentgov[sign]>=1.3.0; extra == 'sign'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Interlock
|
|
35
|
+
|
|
36
|
+
**A reference monitor for agent side effects.** An agent proposes a plan. It holds no
|
|
37
|
+
credentials and cannot execute. Interlock applies the plan against a real but uncommitted
|
|
38
|
+
substrate, measures what actually changed, evaluates deterministic predicates against that
|
|
39
|
+
measurement, and only then commits or rolls back.
|
|
40
|
+
|
|
41
|
+
> **Start here:** [the live console](https://crimsondevil0929.github.io/fiduciary/) ·
|
|
42
|
+
> [how AgentGov, Interlock and Fiduciary fit together](#start-here) ·
|
|
43
|
+
> [put it in front of your agent in 30 minutes](https://github.com/crimsondevil0929/fiduciary/blob/main/docs/QUICKSTART.md)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run python demo.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
SCENARIO 2 The identical plan, through Interlock.
|
|
51
|
+
MEASURED by the database, not predicted from the plan:
|
|
52
|
+
rows updated 63 inserted 63 deleted 103
|
|
53
|
+
blast radius 229 rows across 4 tenants
|
|
54
|
+
orders.total $20,925.00 -> $0.00
|
|
55
|
+
|
|
56
|
+
ADJUDICATED by 7 deterministic checks:
|
|
57
|
+
BLOCK blast_radius plan would mutate 229 rows, limit is 8
|
|
58
|
+
BLOCK tenant_isolation plan spans 4 tenants, limit is 1
|
|
59
|
+
BLOCK no_delete plan deletes 103 row(s) from ungranted table(s): order_audit
|
|
60
|
+
BLOCK column_value_guard orders.total falls 100.0% (20,925.00 to 0.00), limit is 30%
|
|
61
|
+
advise stated_footprint agent stated 6 row(s); substrate measured 229 (38.2x)
|
|
62
|
+
|
|
63
|
+
OUTCOME ROLLED BACK
|
|
64
|
+
Byte-identical to before the plan ran. Nothing reached disk.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Start here
|
|
68
|
+
|
|
69
|
+
**See the whole stack working:** <https://crimsondevil0929.github.io/fiduciary/>. Six
|
|
70
|
+
industries run on one engine, a real model's recorded turns are replayed on the current
|
|
71
|
+
release, and 16 audit bundles can be verified in your browser.
|
|
72
|
+
|
|
73
|
+
A gateway decides whether an agent may call a tool. This stack decides whether what the call
|
|
74
|
+
changed may be kept, and produces evidence for someone who trusts neither the agent nor the
|
|
75
|
+
operator. It is three packages:
|
|
76
|
+
|
|
77
|
+
| Package | On PyPI | What it answers |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| [Fiduciary](https://github.com/crimsondevil0929/fiduciary) | [`interlock-fiduciary`](https://pypi.org/project/interlock-fiduciary/) | Who may do what, and who approved each exception. Signed mandates, 2-of-3 quorum warrants, the approvals console, governed MCP tools, and audit bundles anyone can verify offline. **Start here** to put the stack in front of an agent: it installs the other two. |
|
|
80
|
+
| **Interlock** (this repository) | [`interlock-escrow`](https://pypi.org/project/interlock-escrow/) | Does what this write actually changed satisfy policy? It stages the write, measures it, commits it once or puts it back, and delivers outbound calls exactly once. Each change is measured in one of four ways: **T** inside a transaction, **P** as the system's own plan, **V** predicted then read back, **R** the request only. |
|
|
81
|
+
| [AgentGov](https://github.com/crimsondevil0929/agentgov) | [`interlock-agentgov`](https://pypi.org/project/interlock-agentgov/) | May this scope spend this amount? Spend and rate ledgers across a delegation tree, circuit breakers, ARC1 receipts, and reconciliation of a system's own log against them. |
|
|
82
|
+
|
|
83
|
+
New here? [Put it in front of your agent in 30 minutes](https://github.com/crimsondevil0929/fiduciary/blob/main/docs/QUICKSTART.md),
|
|
84
|
+
and read [what it does and does not protect against](https://github.com/crimsondevil0929/fiduciary/blob/main/docs/THREAT_MODEL.md).
|
|
85
|
+
|
|
86
|
+
## Why authorization does not catch this
|
|
87
|
+
|
|
88
|
+
A prompt-injected agent is a fully authorized agent. It is doing something it has
|
|
89
|
+
permission to do, so an authorization check passes. The injected instruction arrived from
|
|
90
|
+
the customer's own database, inside a tool result, after every provider-side filter had
|
|
91
|
+
already run, so a provider-side filter does not see it either. The only remaining place to
|
|
92
|
+
catch it is between the effect being computed and the effect becoming durable.
|
|
93
|
+
|
|
94
|
+
That position requires write-path credentials to the database the effects land in, which is
|
|
95
|
+
why this runs inside the deployment rather than at the model provider.
|
|
96
|
+
|
|
97
|
+
The demo also rules out model quality as the cause. The agent doesn't loop, costs $0.0012
|
|
98
|
+
and writes valid SQL on its first try. The damage isn't a model failure.
|
|
99
|
+
|
|
100
|
+
**`demo.py` uses a scripted agent, not a live model.** `injected_plan` and the rest of
|
|
101
|
+
scenario 2's input are fixed `EffectPlan`s defined in `demo.py`. No model is called. That keeps
|
|
102
|
+
two questions apart: does Interlock stop this plan, and would a real model produce it? The
|
|
103
|
+
demo answers only the first. Current Claude models decline the injected instruction
|
|
104
|
+
shown above in several realistic phrasings. For runs against a real model, see
|
|
105
|
+
[`scripts/live_gauntlet.py`](scripts/live_gauntlet.py): eight scenarios with a real
|
|
106
|
+
`anthropic.Anthropic` client and governed spend. Its results are not reproducible, because
|
|
107
|
+
model behavior varies.
|
|
108
|
+
|
|
109
|
+
## What it measures
|
|
110
|
+
|
|
111
|
+
The diff is read **from the substrate**, never reconstructed from the plan. The two are
|
|
112
|
+
routinely different:
|
|
113
|
+
|
|
114
|
+
| | |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Agent stated | 1 row |
|
|
117
|
+
| SQL `rowcount` | 1 row |
|
|
118
|
+
| **Measured** | **2 rows across 2 tables** |
|
|
119
|
+
|
|
120
|
+
An audit trigger that the plan doesn't mention doubled the footprint. Inspecting the plan
|
|
121
|
+
or parsing the SQL can't show that. Reading the change back from the database does.
|
|
122
|
+
|
|
123
|
+
How a change can be read back depends on the system, and each receipt says which of four
|
|
124
|
+
ways it was:
|
|
125
|
+
|
|
126
|
+
| Class | Measured | Where | What it rests on |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| **T**, transactional | inside the transaction, before it commits | SQLite, PostgreSQL | the database's own capture of every row the plan touched |
|
|
129
|
+
| **P**, plan-bound | the system's own plan of the change, applied only if nothing moved | Git (the diff), Kubernetes (the API server's dry run), Terraform (the plan) | the plan applied is the one measured: a compare-and-swap on the head, version or plan file |
|
|
130
|
+
| **V**, predicted and verified | a prediction, written conditionally, then read back | FHIR servers | the read-back: anything that differs from the prediction is put back and receipted `compensated` |
|
|
131
|
+
| **R**, request-only | the request itself | outbound API calls through the outbox | the vendor's word: delivery is receipted, and its own log is reconciled against the receipts |
|
|
132
|
+
|
|
133
|
+
## Lifecycle
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
PLANNED -> STAGING -> STAGED -> VERIFIED -> COMMITTED
|
|
137
|
+
\-> REJECTED -> ABORTED
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
These properties hold by construction:
|
|
141
|
+
|
|
142
|
+
1. No path from `STAGED` to `COMMITTED` skips adjudication.
|
|
143
|
+
2. `REJECTED` is not overridable in-process. Overriding means submitting a new plan that
|
|
144
|
+
carries an explicit waiver, which is then itself a recorded artifact.
|
|
145
|
+
3. A committed effect is discoverable from the chain. `COMMIT_INTENT` is written, and
|
|
146
|
+
forced to stable storage, before the substrate is told to commit, and `COMMITTED`
|
|
147
|
+
after it returns, so a crash in that window leaves an intent with no terminal record
|
|
148
|
+
after it.
|
|
149
|
+
4. That crash is resolved exactly, without guessing. An intent only says a commit was
|
|
150
|
+
*attempted*, so the substrate also writes a marker row in `_interlock_commits`, inside
|
|
151
|
+
the stage's own transaction. The marker exists if and only if the effects do. On
|
|
152
|
+
restart, `EscrowEngine.recover()` reads the marker for every intent a crashed process
|
|
153
|
+
left open and appends `COMMITTED` or `ABORTED`, noted as recovered. `EscrowRuntime`
|
|
154
|
+
runs it at startup whenever it has a `chain_path`. An intent written before v0.1.2, or
|
|
155
|
+
by a substrate without markers, is left open and logged for an operator, because a
|
|
156
|
+
missing marker proves nothing there. So is one the server hasn't decided yet: a client
|
|
157
|
+
that died mid-`COMMIT` leaves PostgreSQL still committing. A later `recover()` resolves
|
|
158
|
+
it.
|
|
159
|
+
5. A commit whose answer is lost is resolved the same way while the process is alive. If
|
|
160
|
+
the connection drops with `COMMIT` in flight, the engine reads the marker at once. The
|
|
161
|
+
plan is reported committed if it landed, and rolled back only once the server has ended
|
|
162
|
+
the transaction without it. Until the server decides, `execute()` raises
|
|
163
|
+
`CommitUnsettledError` and writes no terminal record. Don't retry the plan then. The
|
|
164
|
+
intent stays open, and `recover()` settles it.
|
|
165
|
+
|
|
166
|
+
## Quickstart
|
|
167
|
+
|
|
168
|
+
`EscrowRuntime` owns the substrate, the record chain, the AgentGov anchor and
|
|
169
|
+
the engine. Five lines to a governed write:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from interlock import EscrowRuntime, TableSpec
|
|
173
|
+
|
|
174
|
+
runtime = EscrowRuntime(
|
|
175
|
+
"prod.db",
|
|
176
|
+
tables=[TableSpec("orders", columns=["id", "tenant", "total"], tenant_column="tenant")],
|
|
177
|
+
scope_id="support-agent",
|
|
178
|
+
)
|
|
179
|
+
result = runtime.execute_sql(
|
|
180
|
+
"UPDATE orders SET total = :total WHERE id = :id",
|
|
181
|
+
{"total": 480.0, "id": 1},
|
|
182
|
+
tenant_id="acme",
|
|
183
|
+
stated_rows=1,
|
|
184
|
+
)
|
|
185
|
+
print(result.committed, result.blocked_by)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
That stages the statement in a real transaction, measures what the database
|
|
189
|
+
actually did, adjudicates the measurement, and commits or rolls back.
|
|
190
|
+
|
|
191
|
+
The runtime takes every option the engine does: `substrate=` in place of the
|
|
192
|
+
path (any substrate, PostgreSQL included), `windows=`, `inbox=`, `sinks=`, and
|
|
193
|
+
`anchor=` for claim and settle. `EscrowRuntime.from_config(config, scope_id=...)`
|
|
194
|
+
builds the one `interlock.toml` describes.
|
|
195
|
+
|
|
196
|
+
**Placeholders must be named.** `Effect.parameters` is a `Mapping`, so the
|
|
197
|
+
driver binds by name: write `:id`, never `?`. A positional placeholder is
|
|
198
|
+
refused when the plan is built, before any lock is taken.
|
|
199
|
+
|
|
200
|
+
### Building a plan
|
|
201
|
+
|
|
202
|
+
For anything past one statement, `PlanBuilder` mints the identifiers and the
|
|
203
|
+
UTC timestamp and wires the dependency DAG:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from interlock import PlanBuilder
|
|
207
|
+
|
|
208
|
+
plan = (
|
|
209
|
+
PlanBuilder(scope_id="support-agent")
|
|
210
|
+
.update(
|
|
211
|
+
table="orders",
|
|
212
|
+
statement="UPDATE orders SET total = :total WHERE id = :id",
|
|
213
|
+
parameters={"total": 100.0, "id": 1},
|
|
214
|
+
tenant_id="acme",
|
|
215
|
+
stated_rows=1,
|
|
216
|
+
)
|
|
217
|
+
.build()
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Effects are sequential by default: each depends on the one added before it.
|
|
222
|
+
Pass `independent=True` for no ordering constraint, or `after=[effect_id]` to
|
|
223
|
+
fan out. `builder.last_effect_id` hands back the id to depend on.
|
|
224
|
+
|
|
225
|
+
### Under the runtime
|
|
226
|
+
|
|
227
|
+
The primitives are unchanged and the runtime hands them back
|
|
228
|
+
(`runtime.engine`, `runtime.chain`, `runtime.anchor`). Wire them yourself when
|
|
229
|
+
you need to:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from interlock import (
|
|
233
|
+
BlastRadius,
|
|
234
|
+
EscrowChain,
|
|
235
|
+
EscrowEngine,
|
|
236
|
+
PlanBuilder,
|
|
237
|
+
SqliteSubstrate,
|
|
238
|
+
TableSpec,
|
|
239
|
+
TenantIsolation,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
substrate = SqliteSubstrate(
|
|
243
|
+
"prod.db",
|
|
244
|
+
tables=[TableSpec("orders", columns=["id", "tenant", "total"], tenant_column="tenant")],
|
|
245
|
+
)
|
|
246
|
+
engine = EscrowEngine(
|
|
247
|
+
substrate,
|
|
248
|
+
checkers=[BlastRadius(50), TenantIsolation(1)],
|
|
249
|
+
chain=EscrowChain("escrow.jsonl"), # omit this and the chain is in memory only
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
plan = (
|
|
253
|
+
PlanBuilder(scope_id="support-agent", intent="refund order 1")
|
|
254
|
+
.update(
|
|
255
|
+
table="orders",
|
|
256
|
+
statement="UPDATE orders SET total = :total WHERE id = :id",
|
|
257
|
+
parameters={"total": 480.0, "id": 1},
|
|
258
|
+
tenant_id="acme",
|
|
259
|
+
stated_rows=1,
|
|
260
|
+
)
|
|
261
|
+
.build()
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
result = engine.execute(plan)
|
|
265
|
+
if not result.committed:
|
|
266
|
+
print(result.blocked_by, result.diff.blast_radius)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`execute()` takes a per-plan `settle_cost=` that overrides the engine's
|
|
270
|
+
default, because what a plan cost to produce is a property of the plan.
|
|
271
|
+
|
|
272
|
+
**The chain file.** `EscrowChain(path)` resumes an existing file. Every record is read and
|
|
273
|
+
verified before the first append, so a restarted process continues the chain instead of
|
|
274
|
+
starting a second one at sequence 1. One file has one writer. The chain claims
|
|
275
|
+
`<path>.lock` for as long as it's open, and a second writer, in this process or another,
|
|
276
|
+
gets `ChainInUseError`. Two writers would fork the chain, and recovery would treat one
|
|
277
|
+
writer's in-flight commits as if it had died. `EscrowChain.load(path)` reads and verifies a
|
|
278
|
+
live chain without claiming it, and can't append. If a crash tears the last line
|
|
279
|
+
mid-append, the next writer cuts it off, because that append never returned. Intents and
|
|
280
|
+
outcomes are fsynced before `append` returns. Pass `fsync=False` to trade that for speed.
|
|
281
|
+
|
|
282
|
+
## What a write costs
|
|
283
|
+
|
|
284
|
+
Each system timed two ways, in alternating rounds on one machine: the write made directly,
|
|
285
|
+
and the same change through Interlock. Through Interlock is the whole path: staged,
|
|
286
|
+
measured, judged by three checkers, committed, recorded in the escrow chain, and receipted
|
|
287
|
+
with an Ed25519 signature. Measured with [`scripts/overhead.py`](scripts/overhead.py)
|
|
288
|
+
on an Apple M2 Pro (macOS, Python 3.11), PostgreSQL 16 in Docker on the same machine, and
|
|
289
|
+
Kubernetes and FHIR on Interlock's loopback servers.
|
|
290
|
+
|
|
291
|
+
| System | Measured as | Direct, median ms | Through Interlock, median ms | Through Interlock, p95 ms | Added, ms | Ratio | Counted per write (direct / through Interlock) |
|
|
292
|
+
|---|---|---|---|---|---|---|---|
|
|
293
|
+
| SQLite | T, in the transaction | 0.20 | 2.17 | 2.93 | +1.97 | 10.6x | file |
|
|
294
|
+
| PostgreSQL 16 | T, in the transaction | 0.96 | 31.93 | 38.56 | +30.98 | 33.4x | connections: 0 / 1 (7.7 ms each here); statements: 1 / 40 |
|
|
295
|
+
| Git | P, the branch's own diff | 122.19 | 382.55 | 464.59 | +260.36 | 3.1x | git processes: 3 / 16 |
|
|
296
|
+
| Kubernetes (loopback API server) | P, the server's dry run | 1.15 | 5.85 | 6.83 | +4.70 | 5.1x | HTTP requests: 1 / 3 |
|
|
297
|
+
| FHIR R4 (loopback server) | V, predicted, then read back | 2.36 | 9.11 | 10.41 | +6.75 | 3.9x | HTTP requests: 2 / 5 |
|
|
298
|
+
| Outbound request (outbox and relay) | R, the request only | 1.08 | 4.42 | 5.11 | +3.34 | 4.1x | HTTP requests: 1 / 1, sent after the commit |
|
|
299
|
+
|
|
300
|
+
200 writes per path (Git: 50); interlock 1.4.3.
|
|
301
|
+
|
|
302
|
+
How to read it:
|
|
303
|
+
|
|
304
|
+
- **SQLite: about 2 ms a write.** The stage, the checks, the chain record and the signed
|
|
305
|
+
receipt, with no network.
|
|
306
|
+
- **PostgreSQL: about 31 ms here, and most of it is round trips.** A stage opens its own
|
|
307
|
+
connection (7.7 ms through Docker's port forwarding) and runs 40 statements, where the
|
|
308
|
+
direct write is one UPDATE on a connection already open. Each statement is a round trip,
|
|
309
|
+
so on a database across a network, multiply 40 by that network's round trip. Stages do
|
|
310
|
+
not reuse connections yet.
|
|
311
|
+
- **Git: git's own processes.** 16 of them against 3 for a commit and push.
|
|
312
|
+
- **Kubernetes and FHIR: two and three more requests.** The dry run and the read-back on
|
|
313
|
+
Kubernetes; the read, the conditional write and the read-back on FHIR. Against a real
|
|
314
|
+
server, each costs that network's round trip.
|
|
315
|
+
- **Outbound: the relay's delivery after the commit,** about 3 ms on top of the POST itself.
|
|
316
|
+
|
|
317
|
+
An agent's tool call already waits hundreds of milliseconds to seconds on its model, so on
|
|
318
|
+
most systems this is a small part of each step. A read never goes through Interlock at all.
|
|
319
|
+
Run `uv run python scripts/overhead.py --postgres "$DSN" --markdown` for your own numbers.
|
|
320
|
+
|
|
321
|
+
## Install
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
pip install interlock-escrow # or, with the stack above it: pip install interlock-fiduciary
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
To work on it, clone it. `agentgov` comes from PyPI (`interlock-agentgov>=1.3.0`, ARC1
|
|
328
|
+
1.4), so a clone needs nothing beside it:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
git clone https://github.com/crimsondevil0929/interlock
|
|
332
|
+
cd interlock && uv sync
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Signing (a relay's attestations, an operator's records) needs the `sign` extra
|
|
336
|
+
(`cryptography`). Verifying never does. `kms-aws` adds `boto3` for `AwsKmsSigner`, a key
|
|
337
|
+
held by AWS KMS, and `otel` adds agentgov's OpenTelemetry metrics. Both pass through to
|
|
338
|
+
agentgov's extras of the same names.
|
|
339
|
+
|
|
340
|
+
**Developing on both repos at once.** `uv sync` resolves agentgov from PyPI, and a sibling
|
|
341
|
+
`../agentgov` checkout is not consulted on its own. To point `uv` at one, add to
|
|
342
|
+
interlock's `pyproject.toml`:
|
|
343
|
+
|
|
344
|
+
```toml
|
|
345
|
+
[tool.uv.sources]
|
|
346
|
+
interlock-agentgov = { path = "../agentgov", editable = true }
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
then `uv lock && uv sync`. It is workspace configuration, not written into the built
|
|
350
|
+
wheel. Drop it, and rerun `uv lock`, before merging: CI resolves a clean clone from the
|
|
351
|
+
lockfile, with no sibling checkout, and fails on a path source.
|
|
352
|
+
|
|
353
|
+
## Development
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
uv sync
|
|
357
|
+
uv run pytest -q
|
|
358
|
+
uv run ruff check . && uv run ruff format --check .
|
|
359
|
+
uv run mypy src/ && uv run mypy --strict tests/ demo.py
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The PostgreSQL tests run against a live server. Set `INTERLOCK_TEST_POSTGRES_DSN` to a
|
|
363
|
+
role that may create databases and roles, and `INTERLOCK_REQUIRE_POSTGRES=1` to fail
|
|
364
|
+
rather than skip when there isn't one. Two suites are release gates.
|
|
365
|
+
`tests/test_crash_consistency.py` kills a real process with `SIGKILL` at every step of a
|
|
366
|
+
PostgreSQL commit, and at random instants, then checks exactly what recovery makes of it.
|
|
367
|
+
`tests/test_tamper_evidence.py` alters every part of the audit trail and checks that
|
|
368
|
+
verification fails exactly there. `INTERLOCK_CRASH_SEED` and `INTERLOCK_CRASH_ROUNDS`
|
|
369
|
+
replay or lengthen the random-instant run.
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
Apache-2.0: see [LICENSE](LICENSE). To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
|
374
|
+
|
|
375
|
+
## Documentation
|
|
376
|
+
|
|
377
|
+
- [docs/GUIDE.md](docs/GUIDE.md): the engine in full. Typing, invariants and partitions,
|
|
378
|
+
refusals and repair, rate windows, AgentGov and Fiduciary, receipts, recovery, extension
|
|
379
|
+
quotes, and [what Interlock does and does not claim](docs/GUIDE.md#scope).
|
|
380
|
+
- [docs/SYSTEMS.md](docs/SYSTEMS.md): each system it stages on (PostgreSQL, Git, Kubernetes
|
|
381
|
+
and Terraform, FHIR), outbound requests through the outbox, and the daemon.
|
|
382
|
+
- [docs/ESCROW_SPEC.md](docs/ESCROW_SPEC.md): the specification, with every requirement
|
|
383
|
+
traced to its tests. [docs/OUTBOX_DESIGN.md](docs/OUTBOX_DESIGN.md) and the `EPIC*_DESIGN`
|
|
384
|
+
documents record how each part was designed.
|
|
385
|
+
- [scripts/overhead.py](scripts/overhead.py): the overhead table above, reproducible.
|
|
386
|
+
- [CHANGELOG.md](CHANGELOG.md), [SECURITY.md](SECURITY.md).
|