diff --git a/README.md b/README.md index 158a360..571ca7e 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,9 @@ the compiled policy allows on truthful evidence.** Remove isolation or the broke and Interlock makes no claim about child-process writes. This honesty is enforced by the broker tests, not just asserted here. +See [Enforcement model — transport is not authority](docs/concepts/enforcement-model.md) +for how this guarantee holds across local, agent, and cloud-sandbox environments. + ## Install **Prebuilt binary (no Go toolchain).** The installer detects your platform, diff --git a/UPSTREAM.json b/UPSTREAM.json index 1055f38..244b9af 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -3,7 +3,7 @@ ".gitattributes": "3d38cf98dcc69b2af4941b76ed3f04e51273fdbd4fc54032e88cf3223b4b9198", ".goreleaser.yml": "cdb7f4f950f7e70c0052205966cb11ef7b9a0b270ff03a670deba696cf1c61b8", "LICENSE": "bd17d47aefaa7406616179288058001b6532881a2517254fe668d7e9c4965cfb", - "README.md": "77979fdd7a0781305d65bf2576cb7368ea339a640f7fab9d6d8ea4a37888e3b0", + "README.md": "39b4cc851a57c654132a73b84d594e61c8a568fd3aed26d86e503133dcdb84c8", "assets/interlock-boundary.png": "9ae73098102d0589bf1bb912af6d39df4d1465eb93e8cea5b7e982e6fa79ef51", "assets/interlock-boundary.svg": "0bbb5aacc90e9311aef732dc2075aa0896ec25d0789b6612a3d98922646a0f1e", "assets/interlock-hero.png": "a30def06d493775458455ed891f1631200a61eafa54fa53d64474a3d340e2757", @@ -78,6 +78,7 @@ "derive/review.go": "59349539a8dbb8664b89b0f2a3cabc50da03e3975727ccae36bda70d3968f704", "derive/schema.go": "4d10bca81110512a10aaf6306d5a5a2ebc4193058b4500cd92022a99b42d4f1e", "doc.go": "ffda943422fc0104acff178f17f096df5d9d0e9065e598aa0d817c457edfb198", + "docs/concepts/enforcement-model.md": "998939bdf003cc0e192fe68ca30d29e5ad76d4682bfeb14d582d40c478dec15d", "e2e/coverage_test.go": "1240b8a56703d3c2b2492ef63049dc4573b769c444549cd693b178c06d3ab136", "e2e/e2e_test.go": "28a8d8c7aa3dfa327b615c00454a438264e2b898abc84fe5c0efb9be0a2fdf3f", "e2e/isolation_test.go": "9498039e244184c8ce2460742af70dd93af9e5eada183119344f2d4d5df222e2", @@ -116,7 +117,7 @@ "generator": "operatorstack/interlock:project-upstream", "schema_version": 1, "source": { - "commit": "4006eba0241ef7c9e0df634d083be7f2888e6fdb", + "commit": "15a45d5d730690e825b3c9cf1de077ec876a0c2b", "path": "labs/21-interlock", "repository": "operatorstack/intelligence-flow" } diff --git a/docs/concepts/enforcement-model.md b/docs/concepts/enforcement-model.md new file mode 100644 index 0000000..d1c8468 --- /dev/null +++ b/docs/concepts/enforcement-model.md @@ -0,0 +1,81 @@ +# Enforcement model — transport is not authority + +Interlock runs in more than one place: a laptop, a local coding agent, a cloud +sandbox, and CI. This document explains what changes between those places and +what does not. + +Read [the enforcement boundary](../../README.md#the-enforcement-boundary-what-is-and-isnt-guaranteed) +first. It defines the three modes and the V1 guarantee. This document adds the +operational view: **the transport changes with the environment; the authority +does not change.** + +## Two rules + +1. **Transport changes.** The transport carries the request to the controller. + The controller answers `allow` or `deny`. The transport is different in each + environment. +2. **Authority does not change.** The broker makes the evidence. The gate blocks + the merge. The authority is the same in every environment. + +The controller is transport. The broker and the gate are authority. Do not +confuse the two. The way a decision is *asked* is not the thing that makes the +decision *trustworthy*. + +## Where Interlock runs + +| Environment | Controller runs | Hooks fire | Extra setup | Result | +|---|---|---|---|---| +| Local (laptop) | On the laptop | Yes | None | Works | +| Local agent (Claude Code, Cursor) | On the laptop | Yes | None | Works | +| Cloud sandbox (Claude Code remote, Devin, Codespaces) | In the sandbox | Yes, after setup | Hydrate the controller. Wire the hooks. | Works after setup | + +In every environment the messages stay local (JSON-Lines over stdio). The sandbox +does **not** need network access to decide. + +## The authority does not move + +### Broker + +The broker makes hash-bound evidence. The broker does the protected action. The +controller cannot fake this. The engine compares claims; the broker makes the +claims truthful. + +The V1 guarantee is Strict mode: the agent cannot modify the protected artifact, +and only the broker can — and only for a request the compiled policy allows on +truthful evidence. The broker tests enforce this, they do not just assert it. See +the [`broker`](../../broker) package. + +### CI gate + +The gate runs Interlock in CI. The gate blocks the merge if the check fails. The +gate runs in one environment you control. The gate runs for every change, from +every environment. + +## Fail-closed + +If the controller does not answer, the result is `deny`. Default deny also applies +when no rule matches. A missing or unreachable controller cannot open the gate. + +## Set up the controller + +- **Do** put the controller in the sandbox. Keep the messages local. +- **Do not** put the controller on another network. A locked sandbox cannot reach + it. Then Interlock denies **every** action. + +Use a network controller only when a local controller is not possible. Then add +authentication and a latency budget. + +## Cloud agents and sandboxes (deferred) + +V1 needs no special code for cloud agents. The gate makes every environment safe. +Whatever the sandbox does, the change must pass the same gate to reach `main`. + +The transport ergonomics for sandboxes — how a request reaches a controller +inside a remote agent — is future work. It is tracked in +[operatorstack/pitot#18](https://github.com/operatorstack/pitot/issues/18). + +## Remember + +- Local hooks give fast feedback. They are not the guarantee. +- The gate gives the guarantee. It runs in one place you control. +- Cloud agents need no special code. Every change must pass the same gate.