Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ the current version is `interlock.__version__` (mirrored into `VERSION` by
### Added
- `interlock.LeaseNotIssued`, a subclass of `InterlockError`, plus `LeaseManager.issued()`
and `LeaseManager.is_revoked()`.
- 90-second demonstrator (`scripts/demo_90s.py`, transcript in `demo/TRANSCRIPT.md`): a narrated
end-to-end run of grant, keepalive, permit, expiry denial, resource-scoped fault gate,
global e-stop, provenance-stamped claim, and ledger audit. Exits non-zero on any unexpected
behavior.

## [0.1.0] — 2026-09-27
### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ claim = ilk.claim(

The fastest path to a first lease is [`docs/guides/quickstart.md`](docs/guides/quickstart.md).

**90-second demonstrator:** `python3 scripts/demo_90s.py` runs the substrate end to end, narrated: grant, keepalive, permit, expiry denial, resource-scoped fault gate, global e-stop, provenance-stamped claim, and ledger audit. It exits non-zero if any step misbehaves. Expected output: [`demo/TRANSCRIPT.md`](demo/TRANSCRIPT.md).

## How it fails

| Condition | Behavior |
Expand Down
23 changes: 23 additions & 0 deletions demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# The 90-second demonstrator

`scripts/demo_90s.py` runs Interlock end-to-end in one terminal, narrated:

1. **GRANT** — authority comes into existence only here, and it expires.
2. **KEEPALIVE** — renewal moves the expiry; it cannot resurrect a dead lease.
3. **PERMIT** — the gate passes: e-stop clear, lease live, no gating fault.
4. **EXPIRED** — a lapsed lease is denied, and the denial is on the record.
5. **FAULT** — a gating fault refuses permits on its resource only (`web:read`
gated, `docs:write` unaffected).
6. **E-STOP** — one engagement halts every permit; release is explicit.
7. **CLAIM** — the run is stamped as a provenance-bearing claim.
8. **AUDIT** — the ledger holds every permit and every denial.

The expected output is captured in [TRANSCRIPT.md](TRANSCRIPT.md); the script
exits non-zero if any step behaves unexpectedly, so the transcript doubles as
a smoke test.

Run it:

```bash
python3 scripts/demo_90s.py
```
12 changes: 12 additions & 0 deletions demo/TRANSCRIPT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[01] GRANT — authority comes into existence only here, and it expires.
[02] lease 8da9a55b030c4424af6f25f633e05157 → docs:write · holder research-agent · ttl 30s
[03] KEEPALIVE — renewal moved the expiry (moved=True). It cannot resurrect a dead lease.
[04] PERMIT — gate passed: e-stop clear, lease live, no gating fault. brief.md written.
[05] EXPIRED — the lapsed lease is denied. The denial is on the record.
[06] FAULT — TOOL_TIMEOUT gates web:read. Faults are resource-scoped: docs:write is unaffected.
[07] PERMIT — docs:write still flows while web:read is gated.
[08] E-STOP — one engagement halts every permit, everywhere.
[09] RELEASE — explicit release only. Permits flow again.
[10] CLAIM — provenance-stamped [inferred]: Demo run: 3 permits granted, 3 denied (expiry, fault, e-stop).
[11] AUDIT — ledger holds 3 permits, 3 denials, 1 claim. Nothing happened off the record.
[12] denial reasons on record: estop, fault, lease_expired
107 changes: 107 additions & 0 deletions scripts/demo_90s.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Interlock 90-second demonstrator.

A narrated end-to-end run of the substrate, in one terminal:

GRANT authority comes into existence only here, and it expires
KEEPALIVE renewal moves the expiry; it cannot resurrect a dead lease
PERMIT the gate passes: e-stop clear, lease live, no gating fault
EXPIRED a lapsed lease is denied
FAULT a gating fault refuses permits on its resource only
E-STOP one engagement halts every permit; release is explicit
CLAIM the run is stamped as a provenance-bearing claim
AUDIT the ledger holds every permit and every denial

Run: python3 scripts/demo_90s.py
"""

from __future__ import annotations

import sys
import time

sys.path.insert(0, "src")

from interlock import EStopEngaged, FaultActive, Interlock, LeaseExpired # noqa: E402

_step = 0


def say(text: str) -> None:
global _step
_step += 1
print(f"[{_step:02d}] {text}", flush=True)


def main() -> int:
ilk = Interlock()

say("GRANT — authority comes into existence only here, and it expires.")
lease = ilk.grant(resource="docs:write", holder="research-agent", ttl_seconds=30)
say(f" lease {lease.id} → docs:write · holder research-agent · ttl 30s")

old_expiry = lease.expires_at
ilk.leases.keepalive(lease)
moved = lease.expires_at > old_expiry
say(f"KEEPALIVE — renewal moved the expiry ({moved=}). It cannot resurrect a dead lease.")

with ilk.permit(lease, action="write", target="brief.md"):
say("PERMIT — gate passed: e-stop clear, lease live, no gating fault. brief.md written.")

short = ilk.grant(resource="docs:write", holder="research-agent", ttl_seconds=1)
time.sleep(1.2)
try:
with ilk.permit(short, action="write", target="late.md"):
pass
say("EXPIRED — UNEXPECTED: lapsed lease was permitted")
return 1
except LeaseExpired:
say("EXPIRED — the lapsed lease is denied. The denial is on the record.")

web = ilk.grant(resource="web:read", holder="research-agent", ttl_seconds=300)
ilk.faults.report(code="TOOL_TIMEOUT", severity="major", resource="web:read")
try:
with ilk.permit(web, action="fetch", target="https://example.com/feed"):
pass
say("FAULT — UNEXPECTED: gated resource was permitted")
return 1
except FaultActive:
say("FAULT — TOOL_TIMEOUT gates web:read. Faults are resource-scoped: docs:write is unaffected.")
with ilk.permit(lease, action="write", target="brief-v2.md"):
say("PERMIT — docs:write still flows while web:read is gated.")

ilk.estop.engage(reason="operator review requested")
try:
with ilk.permit(lease, action="write", target="brief-v3.md"):
pass
say("E-STOP — UNEXPECTED: permit passed under e-stop")
return 1
except EStopEngaged:
say("E-STOP — one engagement halts every permit, everywhere.")
ilk.estop.release()
with ilk.permit(lease, action="write", target="brief-v3.md"):
say("RELEASE — explicit release only. Permits flow again.")

claim = ilk.claim(
text="Demo run: 3 permits granted, 3 denied (expiry, fault, e-stop).",
provenance="demo:scripts/demo_90s.py",
authority=lease.id,
review_after_days=7,
falsifier="re-run scripts/demo_90s.py and compare the transcript",
)
say(f"CLAIM — provenance-stamped [{claim.status}]: {claim.text}")

permits = list(ilk.ledger.read_series("permits"))
denials = list(ilk.ledger.read_series("denials"))
claims = list(ilk.ledger.read_series("claims"))
say(
f"AUDIT — ledger holds {len(permits)} permits, {len(denials)} denials, "
f"{len(claims)} claim. Nothing happened off the record."
)
reasons = sorted({d.get("reason") for d in denials})
say(f" denial reasons on record: {', '.join(reasons)}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading