diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index 795aaa9..8371d3a 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -98,6 +98,7 @@ 7080b3423beded8b64891afdcf2e6f6d8bec6ebeaa88aecbb1b06e149b54ef86 module/module.wasm 7136d8366bbd7c5ccc19a3d810a7f55c31541acec9b0f05bd84e45a39107ded7 tests/test_capability_manifest.py 7426752feb1140b2023192728df35248e4f802617c150bb0523ab97dc1638c0b tools/init-hooks.sh +7499755898c9fe815f8cb5b62314630258bc68fe5d461097ff7f10644508bed3 docs/design/README.md 74d93981f1935d21001ed0475ebb84132ab67c71840ca82ca0cbfac5aca29ea2 docs/contracts/en/wasm-abi.md 763f2814427dc8faaa7fb1ccf3e8afe2f6460273a2dfe852287b3ef66bc43b96 tools/oci-extract/policy_test.go 768d54fe8ec5fde38c41ec075ce02b51d93cf3bf5f47e8d81e33932f3e3ece27 module/ocid_boundary_test.go @@ -157,7 +158,6 @@ b3585cfa2c622b28548424d092f4ee05a679d7ced46a762f7c1f5bc4931e5c27 tools/oci-extr b3d0d0dd71c294b5fa8bad4fcdd9784feae35141aeb71e1755abd8bbd0cfca58 pyproject.toml b4243f2ae7001af0ff5fe747ce968866becba32ec759c0adcf3b7eba008f6ed3 README.hu.md b579f80d62ce67a1be2da3093c3d26e63c4cde31000ddb4fd4787eeda57f423a .gitignore -b5b105b792741f2103911ab66377ca99cc3d06b249a14e97ce94b39849e457c8 docs/design/README.md b5fdf8f066fa2295376d100b62429c7a20edbb04dd0f31f1b7a83507a3374b2e module/testdata/vcn-v1/cases/transport-error/oci/error.json b73da39348121a3ae5bf42915a4ab3c35653b7c9fb5a4c575a9ce0d89da66c7a docs/en/wasm-module-authoring.md b8107f8b819135d4ba3d56804ecca545c24bde54a381f5ddde7ac19815253734 docs/design/forms-integration-direction.md @@ -165,6 +165,7 @@ b8e7c23cfe46a0daf7d3441b15657e9d4b35c4bf50f9421c2841dfbf19cacc82 docs/en/archit b9325f58a95216b240f3715a773e34fe8cdfa4d5fe48a5e7b32002a40be5cc2e tests/test_tools/test_finalize_release.py bb8a1743ab9f77a309bd851b4a1660f6f2420900455a5a0dfb83f4e6905968ff module/abi_manifest_test.go bbe93c718d90a03de5aab2999031c7097bd63a8e40ec14589c8dbe4364a4ec52 renovate.json +bdea227cb29b579b2daaa7fa398102adc096ff9073c78bd9ec1f0dde44b89824 docs/design/relay-requirements.md bef22a55d1559676a57b4a05b48efc7cd226d3d733a1c932e183666679046b53 module/testdata/vcn-v1/cases/drift/expected/evidence.claims.json bfccbd3cd778ac6f0458b7d45d7b25e2cbdc0a789f40e81c1de476f8aab44363 tools/schemalib/loader.py c31a1313ace9ae3da9483d7ac7bbc0938d5e2f6c7c0460d785d94db5bb931e85 docs/contracts/en/release-artifact.md @@ -175,12 +176,12 @@ c6919cec77cbf13916932f91cdc9b9d32d9290282c5d87244af7adc1a30d441a module/go.sum c6f4ed120394d5d71b37e9b44293561cc76be2c7a8087d3facb12458c1b08805 module/m4a_gate_test.go c83bb77196b26e0f656d38b38736a488ff2aa316d995908bf9abade63fccdd67 tools/wasm_fresh_guest_test.sh ca7e4a9412ff28a5ca3e3fd9c759a62126564d82e8892b12b6efa1cbf11d5810 tools/oci-extract/client.go +cb1301d5e401e05e2c14042cb1af909dc2bc819a636b5b4e1533f4c87ed48831 docs/design/specs/approval-and-execution-state.md cb147348de6aadbcef95a82efb66d8e5252d1c82f34aba14c716747dea57d10f module/testdata/vcn-v1/cases/malformed-cidr/expected/evidence.claims.json cb2ab94de33591fc8421f3a9e60a50a7086fc0a24aca6be83c8e383b3753af75 docs/design/roadmap.md cd509e106eb7e827109cca550f46afbcd0ec049c47a719de9debabbbc9c32c57 module/testdata/vcn-v1/cases/transport-error/expected/observed.oci.json cdc268dd275143f3cbad6b84078f397367c40951812143d227b5490cd007ce82 tools/infra.py d08117cca9ab07e9035ddd59f5e7cb0a610517f95b85542988a3222f08a015da module/testdata/instance-fixture.json -d1beaffed059ebbdbee38bc20b465a0e82bb0a4e11b0dc4ee801887cde0f358b docs/design/relay-requirements.md d2c71865652ef4c4f9bed86eaeac6e5a63570e8677987b90a97cb756258ab234 feature-list.md d2dfadbd6532e7de6ae35af273fbdc446ffbb3e7f5b24d7092b3dea70ec2001b module/poll_contract_test.go d3bce87b8745997eb0fb912dcd61c4ee8fd6d6596be6fec41a51649edcf32684 docs/design/specs/canonical-json.md diff --git a/docs/design/README.md b/docs/design/README.md index 68b44b2..f1eb056 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -31,6 +31,9 @@ namespace it opens. Written 2026-07-18 from the design session recorded in computed over. - [specs/plan-digest.md](specs/plan-digest.md) — `plan_digest`, `target_digest`, `execution_digest` and the gate `execute` applies before it signs anything. +- [specs/approval-and-execution-state.md](specs/approval-and-execution-state.md) — M4b (concept): + what an approval names, the execution state of one approval (atomic reservation, UNKNOWN), + the ProofTrace evidence, and the acceptance tests the Relay side will be held to. ## Direction diff --git a/docs/design/relay-requirements.md b/docs/design/relay-requirements.md index 2f89ae0..e94fa44 100644 --- a/docs/design/relay-requirements.md +++ b/docs/design/relay-requirements.md @@ -177,6 +177,159 @@ third-party module author has no way to hit this problem today either. --- +## M4b - approval, authority and execution state (R6 to R10) + +The module can now prove that an `execute` request is internally consistent and echo its +`execution_digest` (M4a, [specs/plan-digest.md](specs/plan-digest.md)). It cannot prove that +anyone allowed it, that it has not run before, or what happened to it. Those belong to the +relay. The behaviour we need, and the tests that will show it, are in +[specs/approval-and-execution-state.md](specs/approval-and-execution-state.md); the items +below are the asks. All are `needed` and **not yet raised** in `CIC-Relay`. Relay facts are +from commit `55a899f`, read, not executed; what was not read is marked NOT VERIFIED. As +above, these state needs, not an implementation: the relay team owns the design. + +Each item: current behaviour, required behaviour, negative test, acceptance condition. + +## R6 - An authenticated, run-bound execution context for the guest, and for its host calls · needed + +**Current behaviour (read).** The cabinet gives a guest an `authContextJson` that is just +the component id (`core/cabinet/service.go:228`) and dispatches only `init`, `process`, +`get` and `notify` (`:234-240`, `core/cabinet/cicwasm.go`). The `cic-flow` host functions +get `cicFlowDeps{sign, doer, egress}` (`cmd/relay/cic_flow_host_funcs.go:51-55`): no run, no +principal, no plan. `sign` and `actuate` each accept a `trace_id` in the request and never +use it (`:112-116`, `:141-148`); it is chosen by the guest, so it cannot be an authority. Beyond that, the two host +functions are independent of each other and of any plan: `sign` signs whatever bytes the +guest hands it (`:112-140`), and `actuate` forwards the guest's own method, URL, headers +(its `Authorization` verbatim) and body to any destination the egress policy allows +(`:141-190`). Neither knows what execution, step or approval a call belongs to. + +**Required.** Each `sign`/`actuate` call is attributable, by the relay's own authenticated +context and not by anything the guest supplies, to one authenticated principal, one run and +one execution (an `execution_digest`). A guest cannot name another execution's context. + +**Negative test.** A guest that sends a forged `trace_id`, or none, is attributed to the +real run; a call made outside any run is refused. + +**Acceptance.** Every provider request in the trace names the authenticated run and the +execution it belongs to. + +## R7 - Approval verification before the first provider call · needed + +**Current behaviour (read).** No approval concept sits on the module's execution path. The +relay has an intent-authorization seam, `core/nexus/ois` (`Intent{Actor, Action, TargetRef, +Reason}`, `Policy.Authorize`), but its only non-test importers are `core/nexus/recorder` +(rollback) and `tools/statechain-demo`; neither `cmd/relay` nor `core/cabinet` uses it. The +model exists; the link to the module's execution breaks there. + +**Required.** Before the first `sign` of an execution the relay verifies an approval that +names the execution's `execution_digest`: authentic, not expired, not revoked, in scope for +the kind and operation, issued by a policy decision that is identified. The module's digest +is compared; the module is not asked whether the approval is genuine. + +**The approval must bind the actual host call, not only the module's entry point (a +required security property).** Verifying a valid `execution_digest` and approval proves +nothing about which HTTP request leaves the relay, because `sign` and `actuate` are +separate host functions that act on what the guest hands them. Before every `actuate` that +can cause a side effect, the relay checks, from its own authenticated context, that the +actual method, target, request body and step belong to the reserved, approved execution; +the set of external requests an execution may make is determined from the approved +execution (for instance through the plan its digest covers), never from what the guest asks +for. The same holds for `sign`: a signature over bytes the relay cannot tie to a step of a +reserved execution is itself a capability, so a `sign` that serves no such step is refused. +A digest or a trace id the guest supplies is not proof of authority. How the relay does +this is its own design; that it does is not optional. The module's own gate (M4a) covers +only the module's `execute` path and cannot protect the host boundary. + +**Negative tests.** +1. No approval; an approval for another `execution_digest`; an expired or revoked one: no + provider request in each case. +2. A valid approval, and the guest sends a different HTTP request (another method, target, + body or step than the approved execution's): no provider request. +3. The guest calls `cic-flow.actuate` (or `cic-flow.sign`) directly, outside any reserved, + approved execution context: refused, no provider request. + +**Acceptance.** The only way a `sign` or `actuate` reaches the provider is through a verified +approval for exactly that execution, and only for the requests that execution is approved to +make. + +## R8 - Atomic, durable single use, and an UNKNOWN state · needed + +**Current behaviour (read).** Nothing in the cic-flow path records that an approval, or a +request, was used; `actuate` turns any transport error into one error text +(`cmd/relay/cic_flow_host_funcs.go:141-190`), so a request that never left and one whose +answer was lost look the same to the guest. NOT VERIFIED: whether the relay has a durable +store it could use, and its guarantees. + +**Required.** The state machine of the spec: an atomic, durable reservation before the +first provider request; a write-ahead record before each provider request; a release only +when no request was attempted; UNKNOWN when a request was attempted and no answer was +recorded, never resent automatically and left only through recorded reconciliation by +evidence (for an asynchronous operation including the correlation of the Work Request with +the execution). Concurrent requests for one approval: at most one executes. A restart +finds the state as left. + +The reservation and the write-ahead record are **safety state, not telemetry**: their +durable recording is fail-closed. If a reservation or a write-ahead record cannot be made +durable, no provider request is sent. They cannot be best-effort. (This is different from the +optional trace recording the cabinet does today: see R9.) + +**Negative tests.** Two or more concurrent uses; a lost answer; a restart after the +reservation; a restart after the write-ahead; a module that refuses with zero sends; the +durable store unavailable or failing at the reservation and at the write-ahead (no provider +request in either case). + +**Acceptance.** For one approval the provider sees the requests of at most one execution, +whatever the concurrency, restarts or lost answers; an execution of unknown outcome is +visible as such; and no provider request leaves while the state that makes it safe cannot be +recorded. + +## R9 - Decision context and the five kinds of evidence in the ProofTrace · needed + +**Current behaviour (read).** The cic-flow host functions at `55a899f` use no audit or +ProofTrace sink (no use of one in `cmd/relay/cic_flow_*.go`, outside tests). R4 above +covers the chained audit entry on the FFI path. The cabinet's ProofTrace recorder is +optional (`nil` skips recording, `core/cabinet/service.go:99`, `:105-106`) and runs once, +after every step has already run, with a recording failure logged and the workflow +continuing (`:299-307`). That is a post-hoc, best-effort record: it is not a write-ahead +record and cannot gate a provider request. + +**Required.** An execution's trace records, as separate kinds of entry, what was requested, +what was authorized, what was sent to the provider, what the provider confirmed, and what +remains uncertain; and carries: the authenticated run id, the policy decision id, the digest +of the verified module artifact, the reservation transition, and the three digests. A signed +observation is its own kind and is never recorded as the proven physical state. + +The evidence that makes an execution safe (the reservation, the write-ahead record, the +transitions of R8) is **durable and fail-closed**; richer or analytic processing of the +trace (display, aggregation, export) may stay optional and best-effort. The two are kept +separate so that losing a display does not lose a safety record, and a missing safety record +never goes unnoticed. + +**Negative test.** An UNKNOWN execution shows an `uncertain` entry and no `confirmed` one; +a durable evidence record that cannot be written stops the execution before the provider +request (R8). + +**Acceptance.** From the trace alone one can tell the five apart for any execution. + +## R10 - `destroy` and `invoke` under the same model · needed + +**Current behaviour (read).** The module has `destroy` and `invoke` as entry points of its +own, with no plan and no digest. NOT VERIFIED: whether the relay sees the operation name +before the guest runs. + +**Required.** An approval is required for them too, naming a digest of the request; the +relay must not route them around R7 to R9. (The module still has to produce that digest: +see "Entry points" in the spec.) + +**Negative test.** A direct `destroy` or `invoke` with no approval: refused, no provider +request; and the same through the host boundary (a guest calling `cic-flow.actuate` +directly with a delete or an action request, R7 test 3). + +**Acceptance.** No operation that can change the provider's state is reachable without an +approval. + +--- + ## How to raise these 1. Keep this file current as needs are discovered or resolved. diff --git a/docs/design/specs/approval-and-execution-state.md b/docs/design/specs/approval-and-execution-state.md new file mode 100644 index 0000000..b4dc0b5 --- /dev/null +++ b/docs/design/specs/approval-and-execution-state.md @@ -0,0 +1,191 @@ +# Approval, authority and execution state (M4b) + +Status: **concept**. Nothing in this document is implemented in the module or the Relay. +The module side (M4a, `plan-digest.md`) proves that a request is internally consistent and +echoes an `execution_digest`; this document says what the Relay has to add so that an +execution is also *authorized*, happens *at most once*, and leaves *evidence*. The Relay is +read-only from this repository: what follows is the behaviour we need from it +(`relay-requirements.md`, R6 to R10), not a design of it. Relay facts are from CIC-Relay at +commit `55a899f`, read, not executed; anything not read is marked NOT VERIFIED. + +## The three questions, kept apart + +| Question | Answered by | Where | +|---|---|---| +| Is this exactly the plan and target we mean? | the module, by deriving it again | M4a, implemented | +| Did an authenticated principal allow this execution, under which policy, within which limits? | the Relay | M4b, concept | +| Was this approval used before, by whom, and what happened? | the Relay, durably | M4b, concept | + +A request that is consistent is not approved; an approval is not proof that the request +matches it (the Relay compares its digest); and a used approval says nothing about whether +the provider acted. They are checked independently and tied together by one value, the +`execution_digest`. + +## What an approval names + +An approval is a record the Relay can authenticate. It names, at least: + +- `execution_digest` (the module's: it binds `plan_digest` and `target_digest`); an approval + for any other digest is not an approval for this execution; +- the approver, and the policy decision that allowed it (an identifier of the decision, not + a bare "yes"); +- its scope: the kind, the operation class, and optionally a specific authenticated run; +- its validity window (not before, not after) and its revocation state; +- the digest of the module artifact that was verified when the approval was bound, so an + approval cannot be spent on a different module build (see the negative tests). + +The module never decides whether an approval is genuine. It only produces the digest. + +## The execution state of one approval + +The Relay keeps one durable record per approval and moves it through these states. Every +transition is recorded before the action it permits, and acknowledged by the store. + +``` +ISSUED --reserve--> RESERVED --about to send step i--> SEND_ATTEMPTED(i) --answer--> ANSWERED(i) + | | | + | +--run ends, zero sends--> RELEASED +--no answer recorded--> UNKNOWN + +--expired / revoked--> VOID +``` + +- **Reservation is atomic and durable.** `reserve` is a compare-and-set from ISSUED to + RESERVED keyed by the approval, recording the authenticated run, the `execution_digest` + and the verified module digest. Of any number of concurrent requests for one approval, + at most one wins; the others are refused. The reservation is committed to durable state + and acknowledged **before the first `sign` or `actuate` of that execution**. A reservation + that only lives in memory is not a reservation. +- **Write-ahead before every provider request.** Before each request that can cause a + side effect leaves the Relay, the Relay records SEND_ATTEMPTED for that step; when the + answer arrives it records ANSWERED with what the provider said. This is what makes a + crash or a lost answer visible afterwards. +- **Zero sends may be released.** The Relay brokers every `actuate`. If a run ends with no + SEND_ATTEMPTED at all (the module refused the request in its own gates, or the run failed + before sending), nothing can have reached the provider, and the reservation may return to + ISSUED (RELEASED) so the approval can be used again, within its validity. Once any + SEND_ATTEMPTED exists the reservation is never released automatically. +- **A component restart** must find the state as it left it: a RESERVED record with no + SEND_ATTEMPTED older than its lease may be released; a SEND_ATTEMPTED with no recorded + answer becomes UNKNOWN. + +The reservation and the write-ahead record are **safety state, not telemetry**. Recording +them durably is fail-closed: if a reservation or a write-ahead record cannot be made durable, +no provider request is sent. They cannot be best-effort, and they are kept apart from any +optional trace processing (see "Evidence"). + +### UNKNOWN is not failure and is not retryable + +The case that decides the design: the Relay sent a provider request and lost the answer +(timeout, restart, a transport error after the request left). It does not know whether the +operation happened. + +- The execution is **not** recorded as failed and the approval is **not** released. +- Nothing is resent automatically: not by the Relay, not by a retry in the module, not by + re-running the same request. A second create can create a second resource. +- The state leaves UNKNOWN only through **reconciliation by evidence**, which is itself + recorded: for an update or an action, an observation compared with the desired state and + the revision; for a create, evidence that the resource exists or provably does not; for an + asynchronous operation, the Work Request correlated with this execution (its id from the + provider's answer; if the answer with the id was the lost one, a correlation by other + evidence, which has to be defined and measured before it is relied on). +- A provider answer that proves the request had no effect (a validation or authorization + refusal) may be ANSWERED as rejected; an ambiguous answer (a 5xx, a gateway timeout) is + not such a proof and stays UNKNOWN until reconciled. +- NOT MEASURED: whether the OCI operations used here accept an idempotency token that the + `execution_digest` could be derived into; nothing here assumes one. + +Reading the Relay at `55a899f` (R8): the guest-facing `actuate` returns every transport +error as one error text, so a request that was never sent and one whose answer was lost look +alike to the guest. That is why the UNKNOWN rule has to be the Relay's. + +## The host boundary + +The module's own gate (M4a) protects only the module's `execute` path. The thing that causes +an external side effect is not that path but the host boundary: `cic-flow.sign` signs the +bytes the guest hands it, and `cic-flow.actuate` forwards the guest's own method, URL, +headers (its `Authorization` verbatim) and body (read at `55a899f`). A valid +`execution_digest` and a verified approval therefore prove nothing about which HTTP request +actually leaves, unless the relay ties the host calls to the approved execution. + +**Required property (not an implementation).** Before every `actuate` that can cause a side +effect the relay checks, from its own authenticated context, that the actual method, target, +request body and step belong to the reserved, approved execution; the requests an execution +may make are determined from the approved execution (for instance through the plan its +digest covers), never from what the guest asks for. A `sign` that serves no step of a +reserved execution is refused too, because a signature over bytes the relay cannot tie to a +step is a capability on its own. A digest or a trace id supplied by the guest is not proof of +authority. Another guest path, another call, or a modified request must not be able to go +around the approval. + +## Evidence (ProofTrace) + +The trace of an execution keeps five things apart, as separate kinds of entry, never +merged into one "result": + +| Kind | What it says | Minimum content | +|---|---|---| +| **requested** | what was asked | authenticated requester, authenticated run id, `execution_digest`, `plan_digest`, `target_digest` | +| **authorized** | what was allowed | `approval_id`, approver, policy decision id, scope and validity, the reservation transition (ISSUED to RESERVED) | +| **sent** | what went to the provider | per step: index, operation, method, URL without secrets, a digest of the request, time, the digest of the verified module artifact | +| **confirmed** | what the provider said back | status, `opc-request-id`, etag, Work Request id, a digest of the answer | +| **uncertain** | what is not known | the UNKNOWN state, why, and what evidence is missing | + +A signed **observation** is its own kind: it says what a read returned, from which source, +at which time. It is never recorded as the proven physical state of the resource. + +The entries are hash-chained as the existing audit mechanism does (R4), and the trace names +the same `execution_digest` the module echoes in its result. + +Two kinds of recording are kept apart. The evidence that makes an execution safe (the +reservation, the write-ahead record, the transitions above) is durable and fail-closed. Richer +or analytic processing of the trace (display, aggregation, export) may stay optional and +best-effort. In the Relay at `55a899f` the cabinet's ProofTrace recorder is optional (`nil` +skips it, `core/cabinet/service.go:99`, `:105-106`) and runs once, after all steps have run, +with a failure logged and the workflow continuing (`:299-307`): a post-hoc, best-effort +record, which is not what the reservation and the write-ahead record need to be. + +## Entry points + +`execute` is the one with a plan and a digest. `destroy` and `invoke` are separate entry +points of the module with no plan: a delete and an action on a named resource. They must +not remain a way around the approval model, so an approval has to be required for them too: + +- Relay side: R10. NOT VERIFIED whether the Relay sees the operation name (`execute`, + `destroy`, `invoke`) before the guest runs; the cabinet at `55a899f` dispatches only + `init`, `process`, `get` and `notify` to a guest (`core/cabinet/service.go:228-240`). +- Module side (**concept**, not built): a digest for `destroy` and `invoke` over the same + framing (operation, kind, the target, and for `invoke` the canonical body), so an approval + can name it. Until it exists the module cannot hand the Relay a value to approve for them. + +`observe` and `poll` only read; whether they need an approval is a policy choice of the +Relay, not decided here. + +## Acceptance tests (to be written when the Relay side exists) + +Each is a test the Relay's owner can run against the mocked provider; "no provider request" +means zero `sign` and zero `actuate` calls at the provider seam. + +| Case | Given | Expected | +|---|---|---| +| No approval | an `execute` with no approval, or an unauthenticated one | no provider request | +| Wrong digest | an approval for another `execution_digest` | refused, no provider request | +| Expired or revoked | an approval outside its window or revoked | refused, no provider request | +| Two concurrent uses | N parallel requests for one approval (N >= 2, repeated) | exactly one reserves; the rest are refused; the provider sees the requests of one execution only | +| Lost answer | the provider seam drops the answer to a sent request | the state is UNKNOWN; after any number of retries and a restart the provider has still seen that request once; no automatic resend | +| Reconciliation needed | an UNKNOWN execution | it leaves UNKNOWN only through a recorded evidence entry | +| Crash after reservation | the component stops after RESERVED, before any send | after restart the approval is releasable (zero sends), never executed twice | +| Crash after write-ahead | the component stops after SEND_ATTEMPTED | after restart the state is UNKNOWN, not RELEASED | +| Module refused | the module refuses the request in its own gates (zero sends) | RESERVED returns to ISSUED; the approval is still usable within its window | +| Changed module artifact | the module digest differs from the one verified when the approval was bound | the approval is not usable; no provider request | +| Destroy or invoke without approval | `destroy` or `invoke` called directly | refused, no provider request | +| A different request under a valid approval | a reserved, approved execution; the guest sends another method, target, body or step than the approved one | refused, no provider request | +| The host function called directly | the guest calls `cic-flow.actuate` or `cic-flow.sign` outside any reserved, approved execution context | refused, no provider request | +| Durable record cannot be written | the store fails at the reservation, and again at a write-ahead record | no provider request in either case | +| Trace shape | any execution, including an UNKNOWN one | the five kinds are present where they apply and distinguishable; an observation is not labelled as proven state | + +## What this does not claim + +Not that the Relay does any of this today (it does not: see R6 to R10), not that the +store, the policy engine or the identity source exist, not that an approval, once +authenticated, makes the plan safe, and not that a reconciled UNKNOWN proves what the +physical state is: it records the evidence that was found. `module.wasm` is not rebuilt, +and a production integration stays on hold until the Relay side exists and is measured.