Skip to content

docs: what destroy and invoke do today, and a binding contract proposal - #46

Merged
sinkog merged 1 commit into
develfrom
docs/destroy-invoke-digest-contract
Nov 8, 2026
Merged

sinkog merged 1 commit into
develfrom
docs/destroy-invoke-digest-contract

Conversation

@sinkog

@sinkog sinkog commented Nov 8, 2026

Copy link
Copy Markdown
Member

What

Documentation and measurement files only: no module code, no WASM change, no Relay code. The plan binding of execute (M4a) holds only for execute; destroy and invoke reach the same host with the same key and are not covered by it. This records a measurement of both entry points, the properties it implies, and a binding contract proposal, in three parts that are kept apart on purpose: the observed behaviour is recorded first, without reference to the contract we would like, and the required properties are derived from it.

  • docs/design/specs/destroy-invoke-binding.md: section 1 measured current behaviour; section 2 the properties derived from it, each tied to an observation (O1 to O7); section 3 a proposed contract (concept); section 4 what can be proved where; section 5 open decisions; section 6 the questions the survey hands to D5.
  • docs/design/measurements/destroy-invoke/: the scratch probe sources, their output and a README with the exact environment and how to rerun, so the tables can be checked.
  • docs/design/README.md: two index entries.

Measured (module eda4aeb, mock host seam, no provider)

  • Destroy and Invoke: every target dimension changes what is signed and sent. resource_id, host, base_path, key_id, revision, one at a time, each gives 1 sign and 1 actuate with the changed value on the wire.
  • Invoke runs any registry operation whatever its role: DeleteVcn (DELETE), UpdateVcn (PUT), CreateVcn (POST), GetVcn (GET, with a body) and the action, each with 1 sign and 1 actuate. The registry-conformance and plan-binding gates of execute do not apply to it.
  • Invoke sends the caller's body text as it is, measured on raw request text: a duplicated key goes out with both values, fields other than the action's go out, an u-escape and number spellings are kept, any JSON value kind (null, array, string) goes out. execute's renderBody filters by role.
  • A repeat signs and sends again, whatever the provider answers. destroy three times with the provider answering 204, 404, 404: results ok, not-found, not-found, but 3 sign and 3 actuate. invoke five times: 5 and 5. The measure of single use is the sign and actuate count, not the provider's answer.
  • No consumer of destroy/invoke in the locally cloned CentralInfraCore repositories or the cic-factory tools, apart from the module's own tests and harness (repositories not cloned here and other environments were not checked).

How to check the measurement

The probes ship in docs/design/measurements/destroy-invoke/ with the environment (go1.25.0, TinyGo 0.39.0, builder image id sha256:b5a3b64b..., Linux 7.0.0-43) and the rerun steps. The recorded output was produced from a clean git archive of eda4aeb, not from the scratch tree the first run used; the first run was on f48246e, the production code files are identical in both commits (empty git diff), and the two outputs are identical apart from timings. Note: the body measurement is on raw request text (a request built with json.Marshal rewrites the text), and the source writes a backslash as "\x5c" so no tool can rewrite an escape sequence in it.

Proposed, not implemented

A per-entry-point execution_digest with its own domain tag (cic-oracle-destroy/1, cic-oracle-invoke/1) over the action kind, the kind, the registry's operation contract and the target (the execute target model); invoke limited to action operations with an object body of the operation's declared fields in strict canonical JSON; the body that is sent defined as one function of the digested value, so the digest is about the HTTP body that leaves and not about the input text. The module would recompute the digest and refuse a mismatch before the first sign. The number form of that function is an open decision and was not measured against OCI.

What this does not claim

Nothing here is implemented. The Relay does not require or verify an approval for destroy or invoke today, and the proposed digest would not make it do so: it lets the Relay approve one content-bound value, and does not by itself stop a call that reaches the provider by another path through the host boundary. The measurement is against a mock host seam, not OCI or the Relay.

Not in this PR

No implementation, no WASM rebuild or signature, no Relay change, no production-integration claim. D5 is not solved here, only the questions that follow from the survey are handed to it. No merge without explicit owner GO.

🤖 Generated with Claude Code

The plan binding of execute (M4a) holds only for execute. destroy and invoke reach the same
host with the same key and are not covered by it. This records, in three kept-apart parts, a
measurement of both entry points, the properties those observations imply, and a contract
proposal; and it ships the scratch probes and their output with the exact environment so the
tables can be checked.

Measured on a clean git archive of devel eda4aeb with the module's mock host seam (no
provider): every target dimension (resource_id, host, base_path, key_id, revision) changes
what is signed and sent, for both entry points; invoke runs any registry operation whatever
its role (delete, update, create, read, action) with one sign and one actuate; invoke sends
the caller's body text as it is (duplicate keys, extra fields, any JSON value kind), where
execute's renderBody filters by role; and a repeated request signs and sends again every time
(destroy 3x gives 3 sign and 3 actuate, invoke 5x gives 5 and 5) whatever the provider answers.

Proposed (concept): a per-entry-point execution_digest with its own domain tag, invoke limited to
action operations with an object body of the declared fields, and the body that is sent defined
as a function of the digested value. What the module can prove, and what stays with the Relay,
is stated, as are the open decisions and the questions handed to D5. Nothing is implemented and
no approval is verified by anything described. Documentation and measurement files only.

---
[signing-metadata]
key = cic-my-sign-key
signature = vault:v1:MEQCIDzLD5giLi2U8hobA5Ul2+Tw7yZSwbqzqqGyoc5vfBC+AiAeADiSN3prfgXXjQh+/0JL53qsTP18H1loo//LwKqxsQ==
hash-algorithm = sha256
digest = fXmh0f8oo29uFEPZlLXpq6iEd2n42q8dU1zzkBJ8exg=

[certificate]
-----BEGIN CERTIFICATE-----
MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw
QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV
BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy
MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb
MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe
i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS
WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0
lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW
-----END CERTIFICATE-----
@sinkog
sinkog marked this pull request as ready for review November 8, 2026 14:07
@sinkog
sinkog merged commit 2ac19ec into devel Nov 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant