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
5 changes: 3 additions & 2 deletions MANIFEST.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -157,14 +158,14 @@ 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
b8e7c23cfe46a0daf7d3441b15657e9d4b35c4bf50f9421c2841dfbf19cacc82 docs/en/architecture.yaml
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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
153 changes: 153 additions & 0 deletions docs/design/relay-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Loading