From bf1f8a7f8c13b1f0aab79984ba8bd77e54d52771 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Thu, 22 Oct 2026 09:31:20 +0400 Subject: [PATCH 1/2] feat(wit): canonize compute host contract Signed-off-by: Joshua J. Bouw --- README.md | 1 + host/compute@1.0.0.wit | 190 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 host/compute@1.0.0.wit diff --git a/README.md b/README.md index 48ae6b2..da09c37 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Each domain is its own package, frozen at a per-file version. A capsule imports | `host/elicit@1.0.0.wit` | `astrid:elicit@1.0.0` | Interactive user input during install/upgrade lifecycle. | | `host/approval@1.0.0.wit` | `astrid:approval@1.0.0` | Human-in-the-loop approval gate for sensitive actions. | | `host/identity@1.0.0.wit` | `astrid:identity@1.0.0` | Multi-platform identity resolve and link. | +| `host/compute@1.0.0.wit` | `astrid:compute@1.0.0` | Draft principal-scoped core-Wasm compute groups, bounded shared memory, jobs, cancellation and accounting. Requires an authenticated host provision and explicit deployment policy. | | `host/guest@1.0.0.wit` | `astrid:guest@1.0.0` | Guest export contract — `astrid-hook-trigger`, `run`, `astrid-install`, `astrid-upgrade`. Each entry point lives in its own world (`interceptor`, `background`, `installable`, `upgradable`) so capsules `include` only what they implement. | A capsule's world declares only the imports it uses plus the guest-export worlds it actually implements: diff --git a/host/compute@1.0.0.wit b/host/compute@1.0.0.wit new file mode 100644 index 0000000..5db83c7 --- /dev/null +++ b/host/compute@1.0.0.wit @@ -0,0 +1,190 @@ +/// Principal-scoped parallel execution of capsule-authored core-Wasm workers. +/// +/// DRAFT. This contract is developed on an unmerged branch. If merged, this +/// package version follows the repository immutability policy. The host is +/// algorithm-blind: the +/// capsule supplies a signed `compute-worker` module; Astrid validates, +/// schedules, meters, cancels, and accounts for it. +/// +/// Workers import one shared linear memory and bounded read-only access to +/// their authenticated, ordered immutable assets. Bulk data and +/// synchronization stay in that memory; this interface is the audited control +/// plane. Each signed `compute-worker` declaration is an object-scoped grant +/// for exactly that component id and digest. + +package astrid:compute@1.0.0; + +interface host { + use astrid:io/poll@1.0.0.{pollable}; + + /// Stable error classification. Strings are diagnostics only. + variant error-code { + /// The capsule declared no valid compute-worker object capability. + capability-denied, + /// A count, range, memory shape, or other request field is invalid. + invalid-input(string), + /// The id is unknown or does not name a `compute-worker` component. + no-such-worker, + /// Hash, imports, exports, memory type, or ABI version is invalid. + worker-invalid(string), + /// Effective principal/operator worker or memory policy denied admission. + quota, + /// A specifically addressed worker already has queued/running work. + busy, + /// Operation raced group, principal, capsule, or kernel cancellation. + cancelled, + /// Worker compile, instantiate, trap, fuel, or forced-interrupt failure. + worker-failed(string), + /// The resource is already terminal or closed. + closed, + /// A control-plane read or write exceeded the per-call transfer limit. + too-large, + /// Non-classified host error. Detail is best-effort. + unknown(string), + } + + /// Whether the runtime may execute more than one worker concurrently. + enum execution-mode { + /// Resolve exactly one worker and dispatch jobs FIFO. + deterministic, + /// Use the resolved parallelism. + parallel, + } + + /// How a requested worker count is resolved. + variant parallelism { + /// Useful host parallelism, bounded by admitted operator policy/reservations. + auto, + /// In parallel mode, admit exactly this positive count or fail with `quota`. + /// Deterministic mode selects one worker regardless of a positive count. + exact(u32), + /// Admit at least one and no more than this count. + at-most(u32), + } + + /// Request to open one compute group over one declared worker artifact. + record group-request { + /// `[[component]] id` whose type is `compute-worker`. + worker: string, + mode: execution-mode, + parallelism: parallelism, + /// Initial 64-KiB shared-memory pages. Must be non-zero. + initial-memory-pages: u32, + /// Maximum pages; zero requests automatic resolution against the worker + /// maximum and remaining policy capacity. A nonzero value must be + /// >= initial; the resolved shared memory always has a maximum. + maximum-memory-pages: u32, + } + + /// Opaque pointer to capsule-defined bytes in the group's shared memory. + record work-descriptor { + /// Byte offset. Must be >= 64 (the ABI header is host-reserved). + offset: u64, + /// Byte length. `offset + length` is checked for overflow and bounds. + length: u64, + /// Application-defined value passed through without interpretation. + tag: u64, + /// `none` schedules the next available worker; `some` pins by index. + worker-index: option, + /// Optional self-limit. The daemon resolves `none` to a finite share + /// derived from effective principal fuel-rate policy, the admitted + /// job maximum and worker count, further narrowed atomically to currently + /// unspent/unreserved capacity. Zero capacity denies admission. Explicit + /// values are admitted exactly or denied; they cannot widen policy. + fuel: option, + } + + enum job-state { + queued, + running, + completed, + cancelled, + failed, + } + + /// Non-blocking snapshot of one job. + record job-status { + state: job-state, + worker-index: option, + } + + /// Terminal result of a worker invocation. + record job-result { + state: job-state, + worker-index: u32, + /// Capsule-defined `astrid_compute_run` return code. + worker-status: s32, + fuel-consumed: u64, + elapsed-ns: u64, + } + + /// Group and principal-attributed counters. Values are host-stamped. + record accounting { + workers-reserved: u32, + memory-bytes-current: u64, + memory-bytes-peak: u64, + jobs-submitted: u64, + jobs-completed: u64, + jobs-cancelled: u64, + jobs-failed: u64, + fuel-consumed: u64, + } + + /// Non-blocking snapshot of one compute group. + record group-info { + worker: string, + mode: execution-mode, + parallelism: u32, + memory-pages: u32, + maximum-memory-pages: u32, + queued-jobs: u32, + running-jobs: u32, + usage: accounting, + } + + /// Observer handle for one submitted invocation. + /// + /// Dropping this handle does not cancel work. The owning group controls the + /// worker lifetime and cancels every queued/running job when dropped. + resource job { + /// Current state without blocking. + status: func() -> result; + + /// Pollable that becomes ready when the job reaches a terminal state. + subscribe: func() -> pollable; + + /// Wait for completion or a typed terminal failure. + join: func() -> result; + + /// Request cancellation. Idempotent. + cancel: func() -> result<_, error-code>; + } + + /// Worker instances plus the shared memory they import. + resource compute-group { + /// Current immutable configuration and mutable counters. + info: func() -> result; + + /// Copy setup/result bytes out of shared memory (maximum 1 MiB/call). + read: func(offset: u64, length: u32) -> result, error-code>; + + /// Copy setup bytes into shared memory (maximum 1 MiB/call). + write: func(offset: u64, data: list) -> result<_, error-code>; + + /// Grow shared memory, returning the previous page count. + grow: func(delta-pages: u32) -> result; + + /// Queue one worker invocation and return immediately. + submit: func(descriptor: work-descriptor) -> result; + + /// Cancel all queued/running work and reject new submissions. + cancel: func() -> result<_, error-code>; + } + + /// Open requires an authenticated package provision bound to the immutable + /// principal UID and capsule. A declaration alone does not grant authority. + /// A failed call returns no usable group. Interrupted startup may leave a + /// worker settling asynchronously; its memory and reservations remain + /// charged until the worker actually exits and cleanup releases them. + open: func(request: group-request) -> result; +} From c4a60dec5b5296730898ea3e9e23bb3bcafcc437 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Thu, 22 Oct 2026 10:12:54 +0400 Subject: [PATCH 2/2] docs(compute): clarify owner authority and worker ABI Signed-off-by: Joshua J. Bouw --- README.md | 2 +- host/compute@1.0.0.wit | 57 +++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index da09c37..6ef2590 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Each domain is its own package, frozen at a per-file version. A capsule imports | `host/elicit@1.0.0.wit` | `astrid:elicit@1.0.0` | Interactive user input during install/upgrade lifecycle. | | `host/approval@1.0.0.wit` | `astrid:approval@1.0.0` | Human-in-the-loop approval gate for sensitive actions. | | `host/identity@1.0.0.wit` | `astrid:identity@1.0.0` | Multi-platform identity resolve and link. | -| `host/compute@1.0.0.wit` | `astrid:compute@1.0.0` | Draft principal-scoped core-Wasm compute groups, bounded shared memory, jobs, cancellation and accounting. Requires an authenticated host provision and explicit deployment policy. | +| `host/compute@1.0.0.wit` | `astrid:compute@1.0.0` | Draft principal-scoped core-Wasm compute groups, bounded shared memory, jobs, cancellation and accounting. Requires a live owner-UID/capsule/package-generation provision and finite deployment policy; see [compute RFC #42](https://github.com/astrid-runtime/rfcs/pull/42). | | `host/guest@1.0.0.wit` | `astrid:guest@1.0.0` | Guest export contract — `astrid-hook-trigger`, `run`, `astrid-install`, `astrid-upgrade`. Each entry point lives in its own world (`interceptor`, `background`, `installable`, `upgradable`) so capsules `include` only what they implement. | A capsule's world declares only the imports it uses plus the guest-export worlds it actually implements: diff --git a/host/compute@1.0.0.wit b/host/compute@1.0.0.wit index 5db83c7..9746a9d 100644 --- a/host/compute@1.0.0.wit +++ b/host/compute@1.0.0.wit @@ -2,15 +2,16 @@ /// /// DRAFT. This contract is developed on an unmerged branch. If merged, this /// package version follows the repository immutability policy. The host is -/// algorithm-blind: the -/// capsule supplies a signed `compute-worker` module; Astrid validates, -/// schedules, meters, cancels, and accounts for it. +/// algorithm-blind: the capsule supplies signed `compute-worker` bytes; +/// Astrid validates, schedules, meters, cancels, and accounts for them. /// -/// Workers import one shared linear memory and bounded read-only access to -/// their authenticated, ordered immutable assets. Bulk data and -/// synchronization stay in that memory; this interface is the audited control -/// plane. Each signed `compute-worker` declaration is an object-scoped grant -/// for exactly that component id and digest. +/// Workers import one bounded shared memory32 or memory64 and may use ABI-1 +/// read-only imports for their authenticated, ordered immutable assets. Bulk +/// data and synchronization stay in that memory; this is the audited control +/// plane. A signed declaration identifies candidate bytes, not execution +/// authority. The host independently provisions the current principal UID, +/// capsule and exact published generation, including worker/asset digests, and +/// rechecks the live registry at activation and open. package astrid:compute@1.0.0; @@ -19,7 +20,8 @@ interface host { /// Stable error classification. Strings are diagnostics only. variant error-code { - /// The capsule declared no valid compute-worker object capability. + /// No current authenticated owner/capsule/generation provision, or its + /// publication was replaced or revoked in the live registry. capability-denied, /// A count, range, memory shape, or other request field is invalid. invalid-input(string), @@ -29,7 +31,8 @@ interface host { worker-invalid(string), /// Effective principal/operator worker or memory policy denied admission. quota, - /// A specifically addressed worker already has queued/running work. + /// A targeted worker already has one pending/running job, or an + /// untargeted dispatch meets a full bounded worker queue. busy, /// Operation raced group, principal, capsule, or kernel cancellation. cancelled, @@ -55,10 +58,11 @@ interface host { variant parallelism { /// Useful host parallelism, bounded by admitted operator policy/reservations. auto, - /// In parallel mode, admit exactly this positive count or fail with `quota`. + /// Zero is invalid. In parallel mode, admit exactly this positive count + /// or fail with `quota`. /// Deterministic mode selects one worker regardless of a positive count. exact(u32), - /// Admit at least one and no more than this count. + /// Zero is invalid. Admit at least one and no more than this count. at-most(u32), } @@ -72,13 +76,13 @@ interface host { initial-memory-pages: u32, /// Maximum pages; zero requests automatic resolution against the worker /// maximum and remaining policy capacity. A nonzero value must be - /// >= initial; the resolved shared memory always has a maximum. + /// >= initial; the resolved shared memory always has a finite maximum. maximum-memory-pages: u32, } /// Opaque pointer to capsule-defined bytes in the group's shared memory. record work-descriptor { - /// Byte offset. Must be >= 64 (the ABI header is host-reserved). + /// Byte offset. Must be >= 64 (the advisory ABI header occupies 0..64). offset: u64, /// Byte length. `offset + length` is checked for overflow and bounds. length: u64, @@ -89,8 +93,9 @@ interface host { /// Optional self-limit. The daemon resolves `none` to a finite share /// derived from effective principal fuel-rate policy, the admitted /// job maximum and worker count, further narrowed atomically to currently - /// unspent/unreserved capacity. Zero capacity denies admission. Explicit - /// values are admitted exactly or denied; they cannot widen policy. + /// unspent/unreserved capacity. Zero capacity and `some(0)` deny admission. + /// Explicit positive values are admitted exactly or denied; they cannot + /// widen policy. Worker startup also requires finite fuel and a timeout. fuel: option, } @@ -118,7 +123,8 @@ interface host { elapsed-ns: u64, } - /// Group and principal-attributed counters. Values are host-stamped. + /// Group and principal-attributed counters. Values are host-stamped, but + /// owner UID, generation, digests and assets require a separate host audit. record accounting { workers-reserved: u32, memory-bytes-current: u64, @@ -165,24 +171,29 @@ interface host { /// Current immutable configuration and mutable counters. info: func() -> result; - /// Copy setup/result bytes out of shared memory (maximum 1 MiB/call). + /// Copy bytes out of retained shared memory (maximum 1 MiB/call). + /// Cancelled groups may permit reads until the resource is retired. read: func(offset: u64, length: u32) -> result, error-code>; - /// Copy setup bytes into shared memory (maximum 1 MiB/call). + /// Copy bytes into retained shared memory (maximum 1 MiB/call). + /// Cancellation alone does not retire the memory; retirement closes access. write: func(offset: u64, data: list) -> result<_, error-code>; - /// Grow shared memory, returning the previous page count. + /// Grow within the admitted maximum, returning the previous page count + /// and updating accounting/header. Closed groups reject growth. grow: func(delta-pages: u32) -> result; /// Queue one worker invocation and return immediately. submit: func(descriptor: work-descriptor) -> result; - /// Cancel all queued/running work and reject new submissions. + /// Cancel all queued/running work and reject new submissions/growth. + /// This is not evidence that native workers exited or RAM was released. cancel: func() -> result<_, error-code>; } - /// Open requires an authenticated package provision bound to the immutable - /// principal UID and capsule. A declaration alone does not grant authority. + /// Open requires a live, independently authenticated package provision bound + /// to immutable principal UID, capsule and exact publication generation, + /// checked against the live registry. A declaration alone grants nothing. /// A failed call returns no usable group. Interrupted startup may leave a /// worker settling asynchronously; its memory and reservations remain /// charged until the worker actually exits and cleanup releases them.