Skip to content

RFC: Host ABI#22

Open
joshuajbouw wants to merge 16 commits into
mainfrom
rfc/host-abi
Open

RFC: Host ABI#22
joshuajbouw wants to merge 16 commits into
mainfrom
rfc/host-abi

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Formalizes the host ABI as the typed contract between the Astrid kernel and WASM capsule guests, and locks in the pre-1.0 evolution discipline that makes the ABI safe to change post-1.0 without breaking third-party capsules.

Scope

Final 1.0 contract shape:

  • 12 per-domain packages under astrid:* (fs, ipc, uplink, kv, net, http, sys, process, elicit, approval, identity, guest).
  • Typed Component Model resources for long-lived handles: file-handle, unix-listener, tcp-listener, tcp-stream, udp-socket, http-stream, subscription, process-handle. Drop is automatic; no manual close.
  • Per-package error-code variants replace stringly-typed result<_, string>. Specific arms (not-found, capability-denied, would-block, boundary-escape, airlock-rejected, etc.) + catch-all unknown(string).
  • principal-attribution variant on ipc-message distinguishes kernel-verified from uplink-claimed principals. Closes the ipc-publish-as ambient-spoofing vector.
  • Pollable composition via subscribe-readiness / subscribe-readable / subscribe-exit / subscribe-logs methods returning wasi:io/poll.pollable. Bridge and fan-out capsules multiplex bus + sockets + HTTP + child processes via wasi:io/poll.poll.
  • Per-export worlds in astrid:[email protected] (interceptor / background / installable / upgradable). Capsules include only what they implement.

Namespace split: capsule-to-capsule IPC event schemas moved to a distinct astrid-bus:* namespace (interfaces/*.wit) to resolve collisions with the kernel ABI and codify the layering. Capsule.toml imports split between [imports.astrid] (host) and [imports.astrid-bus] (bus contracts).

WASI exposure narrowed. Only wasi:io/poll + wasi:io/streams retained in the kernel linker — these are CM foundation types backing the pollable composition story. Everything else from WASI (filesystem, sockets, clocks, random, cli) is no longer exposed: those operations would bypass Astrid's capability gates, principal scoping, and audit chain. Random / monotonic clock / sleep / wall-clock live under astrid:sys instead.

Pre-1.0 evolution discipline (the three coordinated rules from astrid#750):

  1. Per-domain packages so bumping astrid:net doesn't recompile capsules that only import astrid:kv.
  2. Multi-version kernel registration — every supported (package, version) registered explicitly into the wasmtime CM linker, with host-side shims between versions.
  3. Frozen WIT files per versionhost/<name>@X.Y.Z.wit is immutable after main. New shapes ship as new files at new versions. Enforced by CI lint.

Scope boundary: governs the kernel↔capsule WIT contract. Capsule↔capsule lives on the IPC bus under astrid-bus:*, governed separately by topic-versioning convention (*.v1.**.v2.*).

Implementation status

The 1.0 WIT contract landed across 14 commits on unicity-astrid/wit#feat/per-domain-split:

  • Initial per-domain split + frozen-file CI lint
  • Per-export worlds for astrid:guest
  • Drop trigger-hook; add random-bytes / clock-monotonic-ns / sleep-ns to astrid:sys
  • 1.0 robustness adds: fs-append / copy / rename / remove-dir-all, UDP set, process stdin/env/cwd/signal, kv-cas
  • Std-shape corrections: file-stat extended with is-file / is-symlink / mode / mtime-ns / ctime-ns / atime-ns; HTTP body bytes; signed exit-code; option semantics
  • TCP keepalive + linger
  • Vendor wasi:[email protected]; validate-wit.sh for deps/-staged parsing
  • Namespace split astrid:* (host) / astrid-bus:* (bus)
  • Bucket A pass on all 12 host packages: error-code variants, resource types, principal-attribution variant
  • Doc-as-contract additions: VFS-scheme path return rule, TOCTOU re-validation contract, charset rules, per-fn audit policy, cumulative byte quotas, get-interceptor-bindings scope restriction
  • Pollable foundation on subscription / unix-listener / tcp-listener / tcp-stream / udp-socket / http-stream / process-handle
  • Late additions: inbound tcp-listener, hard-link creation, fs-mkdir / fs-mkdir-all split, UDP connected mode, SO_REUSEADDR, process os-pid / wait-with-output, http-stream pollable

All commits GPG-signed; CI's wit-immutability + wit-parses both green.

Deferred to 1.x (intentional, each a clean minor bump)

  • Symlink creation — sandbox-escape risk outweighs demand; read-only fs-read-link / fs-stat-symlink retained.
  • File locking — kv-cas covers in-runtime concurrency; cross-process locks aren't useful in a sandboxed runtime.
  • UDP multicast — niche.
  • HTTP outgoing body streaming — needs proper outgoing-body resource design.
  • Additional socket options beyond keepalive / linger / reuseaddr.
  • Async WIT (async func) — 2.0 path; deferred so 1.0 stays sync.

Audit basis

Pre-freeze contract validated against three external references (commits referenced in the wit PR):

  1. Rust std — every std::fs / std::net / std::process / std::time primitive that wraps a syscall, mapped against the host ABI.
  2. WASI Preview 2wasi-filesystem, wasi-sockets, wasi-http, wasi-clocks, wasi-random, wasi-io, wasi-cli source .wit files reviewed for naming / shape / resource conventions.
  3. Capability-based security + WebAssembly security guidance — CWE-22 / CWE-367 / CWE-400 / CWE-918, Bytecode Alliance + WebAssembly.org security docs, capability-leak / confused-deputy / TOCTOU / resource-exhaustion / forgery / sidechannel vectors.

Each surfaced finding either landed in the WIT or has a documented reason for being deferred (security risk, niche, needs design pass).

What changed in this RFC update sequence

  • Added Summary paragraph + new "ABI evolution discipline" section with the three coordinated rules.
  • Updated framing from Extism/JSON wire format to Component Model native transport.
  • Pointed the canonical spec at unicity-astrid/wit (host/<name>@<version>.wit).
  • Aligned with the wit-repo split: astrid:guest for lifecycle exports (was astrid:hook, collided with the bus interfaces/hook.wit); minimal astrid:core proposal dropped; frozen-file path corrected to host/<name>@<version>.wit.
  • Recorded the per-export world split for astrid:guest and its toolchain-stub motivation.
  • Added the Versioning rules subsection: minor for purely orthogonal additions (new top-level functions on existing interfaces only); major for any record / enum / signature touch.
  • Replaced the original "WASI primitives also available" subsection (which promoted broad WASI use) with a "Foundation primitives — minimal WASI retention" subsection that documents the narrow wasi:io retention and the rationale for not exposing the rest.
  • Added a "Capsule-to-capsule contracts live in a distinct astrid-bus:* namespace" subsection.
  • Rewrote Guest exports section around per-export worlds.
  • Rewrote Error handling section around typed per-package error-code variants.
  • Host interfaces table rewritten to describe each package's resource + function shape rather than function count.
  • Capability model gains net_udp and net_tcp_bind.
  • Drawbacks + Rationale references corrected for the resource-heavy final shape.
  • Resolved prior open questions on ABI versioning independence, deprecation path, world naming convention, and astrid:[email protected] cleanup.
  • Remaining open question: resource-typed caller-context (alternative to caller-context record on astrid:sys); support-window length (decision deferred until first post-1.0 minor bump).

Replaces closed PR #18 which described the initial syscall set.

Tracking: astrid#573, astrid#750.

55 host functions across 13 domain interfaces, 4 guest exports.
Capability-gated, audited, per-principal scoped. Canonical WIT spec
at core/wit/astrid-capsule.wit.
51 host functions across 12 interfaces (not 55/13). All function
names and per-interface counts verified against astrid-capsule.wit.
…solved questions

The WIT file is the canonical spec — the RFC now references it instead of
duplicating function tables that go stale. Added: capability model summary,
VFS scheme table, KV scoping explanation, 5 substantive unresolved questions.
Adds the three coordinated rules that make the host ABI evolvable
post-1.0 without breaking third-party capsules on every WIT touch:

1. Per-domain packages — replace monolithic astrid:[email protected] with
   one package per domain (astrid:ipc, astrid:net, astrid:kv, ...)
   plus a minimal astrid:core for cross-cutting types only.
2. Multi-version kernel registration — kernel explicitly registers
   every supported (package, version) pair into the wasmtime CM
   linker, with host-side shims translating between version shapes.
3. Frozen WIT files per version — once shipped, interfaces/<name>@X.Y.Z.wit
   is immutable; shape changes ship as new files at new versions,
   enforced by CI lint in unicity-astrid/wit.

Also updates the RFC to reflect that Component Model is the current
transport (Extism/JSON wire-format framing removed), points the
canonical spec at unicity-astrid/wit, scopes the discipline to
kernel<->capsule (capsule<->capsule is IPC-bus territory, not WIT),
resolves the prior 'ABI versioning independence' and 'deprecation
path' unresolved questions, and adds new open questions on minimal-
core vs zero-shared and resource-typed caller-context.

Tracking issue astrid#750 added alongside astrid#573.
…re, add astrid:guest

Follow-up to the per-domain split in unicity-astrid/wit#feat/per-domain-split:

- Frozen-file path is host/<name>@<version>.wit, not interfaces/<name>@<version>.wit.
  The interfaces/ directory in the wit repo is already capsule-to-capsule
  IPC contract territory; the host ABI lives at host/.
- astrid:core is dropped. Auditing the actual types in the monolithic
  astrid-capsule.wit showed nothing genuinely cross-cutting: caller-context
  is sys-only, principal is option<string> (a primitive), error shapes are
  result<T, string>. Zero-shared is purer and cheaper. Open question and
  drawback updated to reflect the decision.
- astrid:[email protected] added for the lifecycle export contract
  (astrid-hook-trigger, run, astrid-install, astrid-upgrade). Named 'guest'
  rather than 'hook' to avoid collision with the unrelated capsule-to-capsule
  astrid:[email protected] in interfaces/hook.wit. Symmetric with the 'host'
  interfaces — host is kernel-side, guest is capsule-side.
- Rule 1 package list reordered to match the actual file order in the
  wit repo for grep-friendliness; cron removed since it does not exist
  in the WIT (it was an RFC table artefact, not a real interface).
- Rule 3 lint description tightened: 'modifies, deletes, or renames a
  file matching *@X.Y.Z.wit that exists on the base ref' — new files
  matching the pattern are the legitimate evolution path.
Reflect the resolution adopted in unicity-astrid/wit@feat/per-domain-split:
`astrid:[email protected]` exposes four per-export worlds (`interceptor`,
`background`, `installable`, `upgradable`) rather than a single
`exports` world bundling all four entry points.

The rationale comes from a concrete kernel cost: the wasm32-wasip2
toolchain auto-stubs mandatory exports the source crate does not
implement, which forced the kernel into binary-parsing stub detection
to avoid treating every interceptor-only capsule as a run-loop daemon
(astrid-capsule's STUB_PRONE_EXPORTS hack). Per-export worlds eliminate
the cause — exports appear in the binary only when implemented.

Updates the rule-1 narrative, the worked example, and folds the now-
resolved open questions on world naming and `astrid:[email protected]`
cleanup into a single retrospective note.
…folding

Three changes, single commit:

- Add a 'Versioning rules' subsection under the evolution discipline.
  Minor for purely orthogonal additions (new top-level functions on
  existing interfaces only). Major for any record/enum shape touch,
  signature change, or removal. Worked table of changes and rationale.
  Notes Capsule.toml's Cargo-style ^constraints and how the kernel's
  multi-version registration reconciles them with the linker's exact-
  match semantics.

- Add a 'WASI primitives also available' subsection. The kernel adds
  wasmtime_wasi::p2 to the linker, so capsule authors should reach for
  wasi:random, wasi:clocks/monotonic-clock, wasi:clocks/wall-clock, and
  wasi:io/streams directly. astrid:* exists where Astrid layers
  capability gating, principal scoping, or audit on top of a syscall;
  where no such layering is needed, WASI is the right surface.

- Correct the host-interfaces table: 65 functions across 11 packages
  plus astrid:guest (was 51 functions across 12 with a non-existent
  'cron' row). Update capability list to drop 'cron'.

- Strip retrospective scaffolding ('earlier draft proposed', 'previous
  open questions resolved', 'STUB_PRONE_EXPORTS carries that hack
  today'). Leaves the doc reading as a specification rather than a
  changelog.
Synthesizes the entire freeze-prep refactor that landed on
unicity-astrid/wit#feat/per-domain-split into the spec.

Summary: rewritten to introduce typed resources, per-package error
variants, and the astrid-bus:* namespace split alongside the existing
per-domain / multi-version / frozen-file rules. Function counts dropped
in favour of describing the surface shape (resources + factories +
typed errors), since the count is now meaningfully different for each
package (resource methods vs flat functions).

Host interfaces table rewritten: lists each package's surface character
(file-handle resource + path ops, tcp-listener / tcp-stream / udp-socket
resources, etc.), the per-package error story, and the capability sets
including the new net_udp and net_tcp_bind allowlists.

WASI subsection replaced. The 'WASI primitives also available' table
that promoted wasi:random / wasi:clocks / wasi:io to capsule authors
turned out to be wrong policy — those primitives bypassed Astrid's
capability gates, principal scoping, and audit chain. New 'Foundation
primitives — minimal WASI retention' subsection explains the narrow
retention (wasi:io/poll + wasi:io/streams only) for the pollable
composition story, and that random/clocks/sleep/wall-clock live under
astrid:sys instead.

New 'Capsule-to-capsule contracts live in a distinct astrid-bus:*
namespace' subsection documents the namespace split between kernel ABI
(astrid:*) and IPC bus event schemas (astrid-bus:*). Capsule.toml
example covers both prefixes.

Guest exports section rewritten around per-export worlds (interceptor /
background / installable / upgradable). Capsules include only what they
implement; toolchain emits exports only when their world is included;
kernel uses plain export-presence checks (no stub detection).

Error handling section rewritten around per-package error-code variants
with specific arms + unknown(string) catch-all. Documents the constraint
that error strings never leak host real-paths / IPs / UUIDs / capability
names; the enumerated arms are the contract.

Drawbacks updated — surface description matches the new resource-heavy
shape; result<_, string> mentions in the rule-1 narrative corrected.

Capability model gains net_udp and net_tcp_bind.

No structural changes — same RFC sections, just brought current with the
implementation.
joshuajbouw added a commit to astrid-runtime/wit that referenced this pull request May 21, 2026
Splits the monolithic `astrid:[email protected]` host ABI into per-domain
frozen packages and applies the full pre-1.0 audit-driven hardening.
Resolves
[astrid-runtime/astrid#750](astrid-runtime/astrid#750)
and aligns with
[rfcs#22](astrid-runtime/rfcs#22) (host_abi
RFC).

This PR is the 1.0 freeze candidate for the WIT contract. Every shape
decision here is irreversible after merge (per the frozen-file rule the
PR also lands), so the contract has been audited against Rust `std`,
WASI Preview 2, and capability-based security guidance.

## 12 host packages — `astrid:*` (kernel ABI)

Each is a frozen versioned file under `host/`. Every host call is gated,
principal-scoped, and audited.

| Package | Surface |
|---|---|
| `astrid:[email protected]` | `file-handle` resource
(open/read-at/write-at/sync-data/sync-all/stat/set-len) + path ops
(exists/mkdir/mkdir-all/readdir/stat/stat-symlink/unlink/read-file/write-file/append/copy/rename/remove-dir-all/canonicalize/read-link/hard-link/open).
`file-type` enum, `datetime` record. |
| `astrid:[email protected]` | `subscription` resource
(poll/recv/subscribe-readiness) + publish/publish-as.
`principal-attribution` variant on `ipc-message` distinguishing
kernel-verified from uplink-claimed. |
| `astrid:[email protected]` | uplink-register/send. `uplink-profile` enum. |
| `astrid:[email protected]` |
get/set/delete/cas/list-keys/list-keys-page/clear-prefix. |
| `astrid:[email protected]` | `unix-listener`, `tcp-listener` (inbound TCP),
`tcp-stream` (full I/O surface + TCP options), `udp-socket` (unconnected
+ connected modes). Factories:
bind-unix/bind-tcp/connect-tcp/udp-bind/lookup-host. |
| `astrid:[email protected]` | `http-stream` resource
(status/headers/read-chunk/close/subscribe-readable). `http-method`
variant. SSRF airlock. |
| `astrid:[email protected]` | log, config, get-caller, signal-ready, clock-ms,
clock-monotonic-ns, sleep-ns, random-bytes (u64 length),
check-capsule-capability. |
| `astrid:[email protected]` | `process-handle` resource
(read-logs/write-stdin/close-stdin/signal/kill/wait/wait-with-output/os-pid/subscribe-exit/subscribe-logs).
Factories: spawn/spawn-background. `exit-info { exit-code, signal }`.
Typed `process-signal` enum. |
| `astrid:[email protected]` | elicit/has-secret. Typed `elicit-type` enum +
`elicit-response` variant. Install/upgrade-only. |
| `astrid:[email protected]` | request-approval. Typed `approval-decision`
enum. |
| `astrid:[email protected]` | resolve/link/unlink/create-user/list-links.
Per-operation response types (no JSON-in-WIT). |
| `astrid:[email protected]` | Guest exports as separate per-export worlds
(`interceptor` / `background` / `installable` / `upgradable`). Capsules
`include` only what they implement. |

## Namespace split — `astrid-bus:*` for capsule-to-capsule

Capsule-to-capsule IPC event schemas moved from `astrid:*` to
`astrid-bus:*` to resolve collisions (host's `astrid:approval` and
`astrid:elicit` collided with bus equivalents) and codify the layering.
17 `interfaces/*.wit` files renamed, internal cross-refs updated,
`Capsule.toml` imports split:

```toml
[imports.astrid]       # kernel ABI — direct wasmtime CM linker calls
fs = "1.0.0"
ipc = "1.0.0"

[imports.astrid-bus]   # IPC bus event schemas
llm = "^1.0"
session = "^1.0"
```

## Three coordinated evolution rules (from #750)

1. **Per-domain packages.** Each interface is its own
`astrid:<name>@X.Y.Z` package so bumping `astrid:net` doesn't recompile
capsules that only import `astrid:kv`.
2. **Multi-version kernel registration.** Kernel registers every
supported `(package, version)` pair explicitly into the wasmtime CM
linker. (Kernel work, separate PR.)
3. **Frozen WIT files per version.** `host/<name>@X.Y.Z.wit` is
immutable after main. Shape changes ship as new files. Enforced by
`scripts/lint-wit-immutability.sh`.

## Audit-driven hardening (across three agent audits)

**Typed errors** — every `result<_, string>` replaced with per-package
`error-code` variants
(fs/ipc/net/kv/http/sys/process/approval/elicit/identity/uplink).
Catch-all `unknown(string)` arm keeps escape hatch.

**Resource types** — every long-lived handle migrated from forgeable
`u64` to typed CM resources (`file-handle`, `unix-listener`,
`tcp-listener`, `tcp-stream`, `udp-socket`, `http-stream`,
`process-handle`, `subscription`). Type safety across interfaces,
automatic Drop, per-instance ownership.

**Pollable foundation** — `wasi:[email protected]` (poll + streams) retained in
the linker. `subscribe-readiness` / `subscribe-readable` /
`subscribe-exit` / `subscribe-logs` methods return
`wasi:io/poll.pollable` on subscription, unix-listener, tcp-listener,
tcp-stream, udp-socket, http-stream, process-handle. Bridge / fan-out
capsules can multiplex bus + sockets + HTTP + child-process events via
`wasi:io/poll.poll` without embedding a runtime purely for that.

**`ipc-publish-as` spoofing closed** — `principal-attribution` variant
on `ipc-message` makes verified vs claimed explicit; downstream
capability checks on sensitive actions MUST require `verified`. The
kernel audit-logs both the uplink's true principal and the claimed
principal on publish-as.

**Doc-as-contract** — VFS-scheme path return rule (no host real-paths
leak through canonicalize / read-link / errors), TOCTOU re-validation
contract (kernel re-resolves every path on every call), charset rules
for topics/keys/paths, per-fn audit policy, cumulative byte quotas,
`get-interceptor-bindings` scoped to caller's own bindings (no
cross-capsule capability inference).

**WASI alignment** — `shutdown-how::receive/send/both` (was
read/write/both); `hop-limit` (was TTL); `datetime` record for file
timestamps (was `mtime-ns: option<u64>`, fixes 2554 overflow + supports
pre-1970); `random-bytes: u64` length; `http-method` variant matching
`wasi:http`.

**WASI no longer exposed broadly** — the kernel's `add_to_linker_sync`
will narrow to `wasi:io/poll` + `wasi:io/streams` only (kernel work,
separate PR). `astrid:*` becomes the single host ABI. Random / clocks /
sleep now live on `astrid:sys` so every host call goes through the same
audit/principal layer. `wasi:filesystem` and `wasi:sockets` no longer
exposed (capsules go through `astrid:fs` / `astrid:net` for
capability-gated access).

## Late additions before freeze (rest of the 1.x candidate list)

- **Inbound TCP listener** (`tcp-listener` resource + `bind-tcp`) — for
self-hosted webhook receivers, gRPC endpoints, Prometheus scrape ports.
Gated by `net_tcp_bind`.
- **Hard-link creation** (`fs-hard-link`) — atomic publish patterns,
content-addressed stores. Same-VFS-scheme only.
- **`fs-mkdir` strict / `fs-mkdir-all` recursive** split.
- **UDP connected mode**
(`udp-socket.connect/disconnect/send/recv/peer-addr`) — QUIC,
DNS-over-UDP, syslog-over-UDP. Faster syscall path and built-in
spoofed-peer filtering.
- **`SO_REUSEADDR`** on `tcp-stream` for listener restart cycles.
- **`process-handle.os-pid()`** for cgroup / /proc correlation.
- **`process-handle.wait-with-output()`** closes the read-logs race on
short-lived children.
- **`subscribe-readable` on http-stream** completes the pollable
composition story.

## Intentionally deferred (each clean minor bump if/when demand emerges)

- Symlink creation (`fs-symlink`) — sandbox-escape risk outweighs
demand. Read-only `fs-read-link` / `fs-stat-symlink` retained.
- File locking — `kv-cas` covers in-runtime concurrency; cross-process
locks aren't useful in a sandboxed runtime.
- UDP multicast — niche; mDNS / SSDP can drive future addition.
- HTTP outgoing body streaming — needs a properly-designed
`outgoing-body` resource (mirroring `wasi-http`); 1.x package addition.
- Additional `SO_*` socket options beyond keepalive/linger/reuseaddr —
defaults work for the typical agent capsule.

## Tooling

- `deps/wasi-io/` — vendored `wasi:[email protected]` (poll, streams, error,
world) at the official release tag.
- `scripts/validate-wit.sh` — stages each `host/*.wit` with `deps/` +
sibling host/ files into a tempdir before parsing, so cross-package
`use` clauses resolve. (`wasm-tools` 1.x can't walk deps trees
multi-pass.)
- `scripts/lint-wit-immutability.sh` — fails any PR that modifies /
deletes / renames a published `*@X.Y.Z.wit` file.
- `.github/workflows/lint.yml` runs both on every PR + push to main.

## Parity vs the pre-split monolithic `astrid:[email protected]`

Verified during the per-domain split: 65/65 original host functions
present (identical names, sorted-diff clean), 30/30 referenced types
preserved, 4/4 guest exports preserved verbatim. 4 dead WIT records
dropped (`capsule-context`, `tool-input`, `tool-output`,
`tool-definition` — never referenced in any WIT signature). Subsequent
commits added capabilities (UDP, DNS, TCP listener, hard-link, process
surface extensions, etc.) without removing any.

## Test plan

- [x] CI `wit-immutability` lint passes (no published-file
modifications).
- [x] CI `wit-parses` job passes — `scripts/validate-wit.sh` validates
every `host/*.wit` with proper deps staging.
- [x] Per-export-world `include` semantics validated against synthetic
consumer worlds (interceptor-only, all-four-worlds, realistic
interceptor + ipc/host import).
- [x] Lint negative-case sweep on a scratch branch: modify / delete /
rename of a frozen file → exit 1 with the right marker; adding a new
versioned file → exit 0.
- [x] Resource types and use clauses parse end-to-end via the staged
`deps/wasi-io/` resolution.

## Follow-ups in dependent repos

- `unicity-astrid/astrid` — kernel implementation: narrow
`add_to_linker_sync` to `wasi:io/poll` + `wasi:io/streams`, implement
all the new `astrid:sys` / `astrid:fs` / `astrid:kv` / `astrid:net` /
`astrid:process` fns + resources, wire up pollable on the relevant
resources, drop `trigger_hook` impl, error-code translation in host
impls. Keep cron plumbing alone.
- `unicity-astrid/sdk-rust` — regen bindings against the new shape,
expose pollable wrappers, error-code → SysError translation,
`fan_out_collect` helper alongside existing `request_response`.
- `unicity-astrid/sdk-js` — mirror.
- First-party capsule migrations — prompt-builder + hook-bridge off
`trigger_hook` (to bus-side fan-out); other capsules migrate to
`astrid_sdk::ipc::request_response` for correlation-id patterns.

Refs:
[astrid-runtime/astrid#750](astrid-runtime/astrid#750),
[rfcs#22](astrid-runtime/rfcs#22)
Folds the reattachable-background-process design into the still-draft
astrid:[email protected] interface (no version evolution — nothing has launched):
a persistent tier alongside the ephemeral spawn/spawn-background, where
spawn-persistent returns an opaque principal-scoped process-id backed by a
host-owned registry that survives instance churn (attach/read/signal/wait/
write/stop by id, list-processes/status, durable watch events).

Motivated by the pooled-instance reattach gap (a background process is reaped
when its spawning instance returns to the pool, so the split-tool pattern is
impossible); generalized beyond shell to MCP-stdio hosts, dev-server
supervisors, and log tailers. Per-principal isolated (id is a capability token
re-checked against the creator on every call; spawn refuses owner-fallback
principals), quota-bounded, sandbox-contained (macOS fail-closed: no PID
namespace), auditable, fail-secure. watch publish-authority added as an open
question (ties to topic-grammar). Updates the interfaces table row.
…/SIGCONT, env/secret semantics to process

Rounds out the astrid:process contract before 1.0 freezes the record/enum
shapes (adding fields/variants post-launch would be a major break):

- resource-limits record on spawn-request (max-memory-bytes / max-cpu-ms /
  max-pids / max-open-files) — declared now, enforcement NOT YET SUPPORTED.
- cpu-ms / mem-bytes-peak on process-info — usage readback, NOT YET POPULATED.
- process-signal gains stop (SIGSTOP) and cont (SIGCONT) for pause/resume.
- Documents env/secret semantics: env is the only path in (sandbox strips the
  rest); secrets go via stdin, never args (audited). The remaining open
  question is whether env values may themselves carry secrets vs an explicit
  secret channel — no shape impact either way.
joshuajbouw added a commit to astrid-runtime/wit that referenced this pull request Jun 6, 2026
## Summary

Adds the **persistent process tier** to `host/[email protected]` — the
canonical WIT for the design folded into the host-ABI RFC
([rfcs#22](astrid-runtime/rfcs#22), "Process:
ephemeral and persistent tiers"). No version bump: `astrid:process` is
still draft (pre-launch), so the persistent surface belongs *in*
`@1.0.0` rather than an evolution file.

## Why

Under the dynamic instance pool a `spawn-background` `process-handle` is
owned by the spawning instance and reaped when that instance resets — so
the start-then-read-then-stop split-tool pattern (shell's
`spawn_background_process`/`read_process_logs`/`kill_process`) is
impossible. The persistent tier returns an opaque, principal-scoped
`process-id` backed by a host-owned registry that survives instance
churn, reattachable from any later invocation. Generalizes beyond shell
to MCP-stdio hosts, dev-server supervisors, and log tailers.

## What's added

Alongside the frozen ephemeral `spawn` / `spawn-background` /
`process-handle`:

- `spawn-persistent` → `process-id`; `attach` (re-materialise the
handle, **detach**-on-drop); id-keyed
`read-logs`/`read-since`/`write-stdin`/`close-stdin`/`signal`/`wait`/`stop`/`release-process`;
`list-processes`/`status`/`status-many`; durable `watch`/`unwatch`.
- New types: `process-id`, `process-phase`, `log-cursor`, `log-stream`,
`log-chunk`, `process-info`, `overflow-policy`, `resource-limits`.
- `spawn-request` gains `limits` + persistent-only fields (label,
keep-stdin-open, overflow, log-ring-bytes, max-lifetime-ms,
idle-timeout-ms, exit-retention-ms).
- `error-code` gains `no-such-process` / `registry-full` / `invalid-id`
/ `persist-unsupported`; `process-signal` gains `stop`/`cont`
(SIGSTOP/SIGCONT); `process-info` carries `cpu-ms`/`mem-bytes-peak`.

## Contract vs implementation

The file is the **full contract**; deferred behaviour is tagged `(NOT
YET IMPLEMENTED)` / `(NOT YET ENFORCED)` / `(NOT YET POPULATED)` so the
**shapes are fixed before 1.0 freezes**. The host stubs the persistent
functions, stdin capture, real pollables, resource-limit enforcement,
and usage population until they land (core → SDKs → shell follow).

## Validation

`scripts/validate-wit.sh` parses every `host/*.wit` clean (incl. this
file). One genuine WIT catch: `stream` is a reserved keyword, so
`read-since`'s stream selector is named `which-stream`.

Open question carried from the RFC: whether `watch`'s host-published
`astrid.process.v1.*` topics need a manifest `[publish]` or are a
kernel-authored class (ties to topic-grammar #809); `watch` may be cut
for `status`+bounded-`wait` polling.
…e as an ungated view

Resolves the open Unresolved Question. Adds enumerate-capabilities() to astrid:sys: argument-free, returns the calling capsule's own EFFECTIVE capabilities (what the kernel enforces, not manifest text). Ungated.

Capability posture is structural metadata, not a secret: knowing capsule X holds host_process grants nothing, because capabilities are enforced, not concealed — a caller can only exercise what it was itself granted. So introspection (self-enumerate + the existing per-name check, self or other) stays a read-only VIEW, ungated on sys. It adds no capability; it is just a view.

Records the rejected alternative: gating cross-capsule introspection is security-by-obscurity (the name space is small and published, UUIDs are discoverable, and posture is not sensitive in the first place) and runs against Astrid's enforce-don't-conceal stance — no gate capability, no migration. enumerate-capabilities underpins the Capability delegation future possibility. Extends astrid:sys ([email protected]); WIT to follow on review.
…nifest-scoped, real checker, reuse-not-blocker)

Q1 return shape: enumerate-capabilities returns capability NAMES/categories (host_process, net_connect, fs_read), the dual of check-capsule-capability; scoped args ("claude", host:port, paths) stay kernel-enforcement detail, not in the view.

Q3 semantics: at the capsule level effective == manifest-declared — capsule capabilities are not revoked at runtime; the grant/revoke model is principal-scoped, a separate axis. Dropped the misleading 'runtime-revoked grant does not appear' phrasing.

Q2 scope: check-capsule-capability is brought onto the same kernel-registered capability registry enumerate reads (it answers only a subset today) so the two are consistent — one manifest-derived source of truth.

Q4 motivation: clarified enumerate is a robustness/reuse primitive (a reusable supervisor deployed under varying manifests, and anti-drift), NOT a gate — a fixed-manifest capsule already knows its own caps statically, so cap-derived governance can proceed with static declaration and does not block on this.
joshuajbouw added a commit to astrid-runtime/astrid that referenced this pull request Jun 6, 2026
## Linked Issue

Closes #866

## Summary

Implements the **persistent-process tier** of `astrid:[email protected]`
(merged contract: astrid-runtime/wit#12; design:
astrid-runtime/rfcs#22). A capsule can now spawn a background child that
**outlives the pooled, stateless WASM instance** that started it —
previously an ephemeral `process-handle` is reaped when its instance
resets on return to the dynamic pool, so the split `spawn → read → stop`
pattern across tool invocations was impossible.

The core is a host-owned `PersistentProcessRegistry`, cloned into every
pooled `HostState` exactly like the cancellation `ProcessTracker`, so a
`process-id` survives instance churn. It owns the child (spawned on the
daemon runtime under the same `bwrap`/Seatbelt sandbox as the ephemeral
tier), its per-stream log rings, and its stdin pipe.

## Changes

- **`wit` submodule → merged main** (`8946e98`); committed
`wit-staging/` ships the contract on the published-crate path (zero
drift; `host/` byte-identical).
- **`PersistentProcessRegistry`**
(`engine/wasm/host/process/persistent/`, split into
`mod`/`ring`/`entry`/`ids`/`config` + tests):
- `spawn-persistent` — 256-bit host-minted CSPRNG `process-id`,
lowercase base32 (doubles as an IPC topic suffix); registry stores only
a **keyed BLAKE3 hash**, never the raw token. Refuses the
**owner-fallback** principal (`persist-unsupported`).
- `status` / `status-many` / `list-processes` (non-draining),
`read-logs` (drain) + `read-since` (non-draining, cursor-addressed,
byte-faithful `list<u8>`), `signal` (incl. `stop`/`cont`), bounded
`wait`, `stop` (SIGTERM→grace→SIGKILL, frees the slot),
`release-process`, `write-stdin` / `close-stdin` (via `keep-stdin-open`
capture).
- **Security:** every id-keyed call re-resolves the live `(principal,
capsule)` vs the recorded creator; unknown / wrong-owner / wrong-capsule
/ reaped all collapse to `no-such-process` (no oracle).
- **Lifecycle:** per-principal concurrent + retained-id caps; idle /
max-lifetime / exit-retention TTLs (guest values clamped DOWN to host
ceilings); per-capsule reaper task; kill-all on capsule unload / daemon
graceful shutdown. A per-process monitor task owns the `Child`
(`kill_on_drop` backstop); reaping happens outside the registry lock.
- **`HostState` gains `persistent_processes`**, wired through the engine
(one registry per `WasmEngine`, cloned into the pool + the
lifecycle/hooks construction sites).
- **macOS works by default** — the only weaker property is
orphan-on-daemon-*hard-crash* (Seatbelt has no `die-with-parent`); a
graceful shutdown / unload reaps. Documented, not gated behind
`persist-unsupported`.

## Deferred (honest)

- `attach` (resource-handle materialisation — needs dual-typed
resource-table dispatch; the id-keyed ops ARE its documented
equivalent).
- `watch` / `unwatch` — host-published lifecycle events raise an OPEN
publish-authority question in RFC host_abi; `status` + bounded `wait` is
the working polling alternative.
- WIT `(NOT YET …)` items: resource-limit enforcement, `cpu-ms` /
`mem-bytes-peak`, instance-local pollables.

## Test Plan

### Automated

- [x] `cargo test --workspace` passes — **1990 passed, 0 failed**, incl.
3 new integration tests that spawn **real** processes through the
registry (spawn/status/wait/read + owner-isolation; concurrent-cap
reject + stop-reaps + slot-free; non-draining cursor `read-since`) and
pure-logic unit tests for the log ring, cursor, and id minting.
- [x] No new clippy warnings — the diff is `astrid-capsule` +
`astrid-hooks`, both clean; the pre-existing `sys.rs:365` /
`astrid-workspace` lints are untouched.

### Manual

- `cargo build --workspace` green; `cargo fmt --all --check` clean.

## Checklist

- [x] Linked to an issue
- [x] CHANGELOG.md updated under `[Unreleased]`
joshuajbouw added a commit to astrid-runtime/wit that referenced this pull request Jun 6, 2026
Adds `enumerate-capabilities() -> result<list<string>, error-code>` to
`astrid:sys` — argument-free, ungated, returns the **calling capsule's
own held capability names** (categories like `host_process` /
`net_connect` / `fs_read`, not the scoped arguments within them). The
list dual of `check-capsule-capability`.

Manifest-derived: capsule capabilities are not runtime-revoked (the
grant/revoke model is principal-scoped, a separate axis), so the
declared set == the usable set at the capsule level. Ungated because
capability posture is structural metadata, not a secret
(enforce-don't-conceal).

Resolves the `capability-introspection-depth` unresolved question in the
host-ABI RFC (astrid-runtime/rfcs#22, which the RFC author resolved as
'self-enumerate as an ungated view'). Pre-1.0 DRAFT edit-in-place to
`[email protected]`. `check-capsule-capability` doc clarified as the per-name
dual (self or other).

Host impl + SDK surfaces follow in core / sdk-rust / sdk-js.
…ss spawn

Adds an optional file-injections list to spawn-request: the host materializes
host-verified, read-only bytes at a chosen absolute path inside the child's
existing OS sandbox. Agent-neutral (host never parses the bytes or interprets
target); motivating consumer is un-overridable per-spawn agent governance.

Specifies the write-protection invariant (unwritable by the child AND the
principal's capsule fs surface), the integrity contract (snapshot to a host-owned
path outside every VFS mount, BLAKE3 content-hash pinned + verified before
exposure, hash recorded in the spawn audit, never a live bind of source), and the
per-OS mechanism (Linux --ro-bind; macOS Seatbelt deny file-write* with target
required outside the principal's VFS surface). No new capability — injection is
permitted only into the caller's own child and is strictly a restriction.
Replaces the single source/target/read-only shape with content bytes plus an
injection-placement variant:

- env-pointer(env-var): host materializes the verified snapshot at a host-owned
  path, exposes it read-only, and sets the named env var to it. OS-agnostic
  (Linux + macOS); the capsule supplies only the env-var name. For agents whose
  un-overridable tier is reachable via an env-redirected file (Claude
  CLAUDE_CODE_MANAGED_SETTINGS_PATH, Gemini GEMINI_CLI_SYSTEM_SETTINGS_PATH).
- fixed-path(path): host ro-binds the snapshot at an absolute in-sandbox path.
  Linux-only (bwrap remap); rejected on macOS (a host write to a caller-named
  path would be an escalation). For agents with a fixed enforced path and no env
  redirect (Codex /etc/codex/requirements.toml).

content bytes (not a source VFS path) removes the host-side read, the read gate,
and the home-staged intermediate file. The host owns placement in both modes, so
the macOS arbitrary-write escalation is gone. Drops the always-true read-only
bool; a writable bind is a separate future capability.
joshuajbouw added a commit to astrid-runtime/wit that referenced this pull request Jun 10, 2026
Adds the file-injection record + injection-placement variant and a
file-injections field (every tier) to spawn-request: the host exposes
host-verified, read-only bytes to a spawned child's existing OS sandbox.
Agent-neutral — content bytes are opaque to the host.

Two placement modes, both exposing the same verified bytes read-only:
- env-pointer(env-var): host materializes the snapshot at a host-owned path,
  exposes it read-only, and sets the named env var to it. OS-agnostic (Linux +
  macOS). For env-redirectable tiers (Claude CLAUDE_CODE_MANAGED_SETTINGS_PATH,
  Gemini GEMINI_CLI_SYSTEM_SETTINGS_PATH).
- fixed-path(path): host --ro-binds the snapshot at an absolute in-sandbox path.
  Linux-only (bwrap remap); rejected on macOS with invalid-input. For fixed
  enforced paths with no env redirect (Codex /etc/codex/requirements.toml).

Write-protection invariant: the bytes are unwritable by the child AND the
principal's capsule fs surface. Integrity: snapshot to a host-owned path outside
every VFS mount, BLAKE3 hash pinned + verified before exposure, hash in the spawn
audit, never a live bind. The host owns placement in both modes, so it never
writes to a caller-named host path. Tracks the Host ABI RFC
(astrid-runtime/rfcs#22).
joshuajbouw added a commit to astrid-runtime/astrid that referenced this pull request Jun 16, 2026
## Linked Issue

Closes #881

## Summary

Implements the generic, agent-neutral host primitive from #881: at
process spawn, the host exposes host-verified, **read-only** bytes to a
spawned child's existing OS sandbox (`bwrap` on Linux, Seatbelt on
macOS). The motivating consumer is un-overridable per-spawn agent
governance (a supervised agent reads a policy file a prompt-injected
session cannot rewrite), but the host is **content-opaque** — it never
parses the bytes, and it owns placement, so every agent-specific detail
stays in the adapter and the kernel stays agent-neutral.

The capsule hands the host `content` bytes plus a **placement**
describing how the child should find them. Two modes, both exposing the
same verified bytes read-only:

- **`env-pointer(env-var)`** — the host materializes the snapshot at a
host-owned path, exposes it read-only (Linux `--ro-bind P P`; macOS
Seatbelt `file-read*` allow + a trailing `file-write*` deny on the
literal), and sets the named env var on the child to that path.
OS-agnostic (Linux + macOS); the host owns the path, so there's no
caller-chosen target and no host write to a caller-named path. For
agents whose un-overridable tier is reachable via an env-redirected file
(Claude `CLAUDE_CODE_MANAGED_SETTINGS_PATH`, Gemini
`GEMINI_CLI_SYSTEM_SETTINGS_PATH`).
- **`fixed-path(path)`** — the host `--ro-bind`s the snapshot at an
absolute in-sandbox path. Linux-only (bwrap remap); rejected on macOS
with `invalid-input` (no remap; a host write to a caller-named path
would be an escalation). For agents whose enforced tier is a fixed path
with no env redirect (Codex `/etc/codex/requirements.toml`).

## Changes

- **`astrid-workspace` sandbox**: `RoInjection` +
`ProcessSandboxConfig::with_ro_inject`; Linux `--ro-bind` (after the
writable bind, before `--unshare-all`); macOS Seatbelt `(allow
file-read* (literal target))` + trailing `(deny file-write* (literal
target))`. `SandboxCommand::wrap_with_injections` (no-injection path
byte-identical to `wrap`); non-empty injections on an unsandboxed
platform fail secure.
- **`astrid-capsule` `host/process/inject.rs`**: BLAKE3-hash `content`,
snapshot to a private `TempDir` (host-owned, outside every VFS mount),
**re-read and verify** against the pin (closes the copy→expose TOCTOU);
per placement, emit the sandbox bind spec and (for `env-pointer`) the
host-set env var. RAII guard (the `TempDir`) cleans up on the child's
lifetime owner: a local for `spawn`, the `ManagedProcess` for
`spawn-background`, the registry entry (reaped by value) for
`spawn-persistent`. The spawn audit records `env:<var>=<hash>` /
`path:<p>=<hash>`. The host sets the env-pointer var authoritatively on
the child (not via the inert guest `spawn-request.env`).
- **WIT**: the `wit/` submodule is bumped to the file-injection commit
and `wit-staging` is regenerated, so `bindgen!` produces
`SpawnRequest.file_injections`, `FileInjection { content, placement }`,
and `InjectionPlacement`.
- **Validation**: `env-pointer` var name non-empty / no `=` / no NUL;
`fixed-path` absolute / no forbidden chars / no `..`; per-injection
content cap (1 MiB → `too-large`); empty `file-injections` is a
zero-overhead no-op.

## Test Plan

### Automated

- [x] `cargo test --workspace` passes
- [x] No new clippy warnings (`cargo clippy --workspace --all-features
-- -D warnings`)
- New unit tests: bwrap ro-bind positioning; macOS Seatbelt read-allow +
trailing write-deny ordering; `wrap_with_injections` unsafe-path
rejection; env-var-name and fixed-path (incl. `..`) validation;
write+verify roundtrip + integrity-mismatch detection; 0600 snapshot
mode; empty-injections no-op.

### Manual (optional)

Not applicable — no runtime behaviour change when `file-injections` is
empty (every existing spawn); the injection path is exercised by the
unit tests above.

## Dependencies / merge order

Third leg of a contract-first change — merge in order:

1. astrid-runtime/rfcs#22 (Host ABI RFC — "Read-only file injection")
2. astrid-runtime/wit#15 (canonical WIT)
3. **Re-pin** this PR's `wit/` submodule from the pre-merge branch
commit `b8fdb6f` to the `wit@main` merge commit, then merge this.

The CI submodule ↔ `wit-staging` dirty-check enforces the pin stays
consistent.

## Checklist

- [x] Linked to an issue (#881)
- [x] CHANGELOG.md updated under `[Unreleased]`
joshuajbouw added a commit to astrid-runtime/wit that referenced this pull request Jun 16, 2026
## Summary

Adds the `file-injection` record + `injection-placement` variant and a
`file-injections` field (honored by every tier) to `spawn-request` in
`astrid:[email protected]`. The host exposes host-verified, **read-only**
bytes to a spawned child's existing OS sandbox. `content` is opaque to
the host; the capsule supplies the bytes plus *how the child should find
them*, and the host owns placement, integrity, and exposure.

## Two placement modes (same verified bytes, read-only)

- **`env-pointer(env-var)`** — the host materializes the snapshot at a
host-owned path, exposes it read-only, and sets the named env var to it.
OS-agnostic (Linux + macOS); the host owns the path, so there's no
caller-chosen target and no host write to a caller-named path. For
agents whose un-overridable tier is reachable via an env-redirected
file: Claude `CLAUDE_CODE_MANAGED_SETTINGS_PATH`, Gemini
`GEMINI_CLI_SYSTEM_SETTINGS_PATH`.
- **`fixed-path(path)`** — the host `--ro-bind`s the snapshot at an
absolute in-sandbox path. Linux-only (bwrap remap); rejected on macOS
with `invalid-input` (no remap; a host write to a caller-named path
would be an escalation). For agents whose enforced tier is a fixed path
with no env redirect: Codex `/etc/codex/requirements.toml`.

## Guarantees

- **Write-protection invariant**: the bytes are unwritable by the child
AND by the spawning principal's capsule `fs_*` surface.
- **Integrity contract**: snapshot → host-owned path outside every VFS
mount, BLAKE3 hash pinned + verified before exposure, hash recorded in
the spawn audit, never a live bind of the source bytes.
- **No new capability / no escalation**: rides `host_process`, into the
caller's own child only; the host owns placement in both modes, so it
never writes to a caller-named host path.

## Notes

- Specified in the Host ABI RFC: astrid-runtime/rfcs#22 ("Read-only file
injection").
- `scripts/validate-wit.sh` passes.
- Consumed by the core implementation (astrid-runtime/astrid#890); core
pins this commit as its `wit/` submodule.
- Supersedes the earlier `{source, target, read-only}` shape
(force-pushed): `content` bytes remove the host-side read / read-gate
and the home-staged file, and the two-mode placement makes the capsule
OS-agnostic in `env-pointer` mode while removing a macOS arbitrary-write
escalation present in the prior shape.
…uploads

Document the http contract's first post-1.0 minor evolution: an all-optional request-options record (caller timeouts, redirect policy, response/decompression size caps, https-only, subresource integrity), response-meta, a streaming request body (upload), and response trailers. Records the layer split (host-internal connection reuse and an SDK-side SSE framing helper are deliberately NOT contract changes), the SSRF exclude-list plus per-hop redirect re-validation, the per-call-vs-client-resource and mTLS forks, and the minor-bump classification as the first worked example of the evolution discipline.
Copilot AI review requested due to automatic review settings June 24, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces RFC 0000-host-abi, formalizing Astrid’s host ABI as a typed WASM Component Model contract between the kernel and capsule guests, including the per-domain astrid:* package split, the astrid-bus:* namespace split for bus schemas, and the pre/post-1.0 ABI evolution discipline (per-domain packages, multi-version registration, frozen WIT files).

Changes:

  • Adds a full RFC describing the 1.0 host ABI structure (packages, resources, error-code variants), capability gating/scoping semantics, and minimal WASI retention.
  • Documents ABI evolution/versioning rules and the operational consequences of wasmtime Component Model structural typing.
  • Specifies namespace layering between kernel ABI (astrid:*) and capsule-to-capsule bus schema packages (astrid-bus:*).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread text/0000-host-abi.md
Comment on lines +87 to +89
Every host function checks the calling capsule's declared capabilities before
executing. A capsule without `fs_read` capability cannot call `astrid_fs_read`.
Violations are logged to the audit chain and return an error to the guest.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 28f23d5 — switched the example to the Component Model form astrid:fs/host.read-file.

Comment thread text/0000-host-abi.md Outdated
Comment on lines +918 to +921
**capsule ↔ capsule** communication, which travels over the IPC bus as
typed events on string topics (e.g. `tool.v1.execute.*`). The bus is not WIT-
typed; capsule-to-capsule shape changes manifest at runtime as deserialization
errors or unmatched subscriptions, not at load time as linker errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 28f23d5 — the astrid-bus:* schemas are WIT-defined in interfaces/*.wit, so I reworded this to say they aren't enforced by the wasmtime linker (incompatibilities surface at runtime) rather than "not WIT-typed".

Comment thread text/0000-host-abi.md
Comment on lines +697 to +702
| World | Export | Required if included |
|---|---|---|
| `interceptor` | `astrid-hook-trigger(action, payload)` returning `capsule-result` | Most capsules — handles routed events |
| `background` | `run()` | Capsules with a long-lived IPC subscriber loop |
| `installable` | `astrid-install()` | One-time install hook (may use `elicit` for secrets/config) |
| `upgradable` | `astrid-upgrade()` | Version-upgrade hook |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left as-is: astrid-hook-trigger is the actual export name in the canonical astrid:guest WIT — [email protected]:62 declares export astrid-hook-trigger: func(action: string, payload: list<u8>) -> capsule-result. The table matches the contract exactly, so this isn't legacy naming.

Comment thread text/0000-host-abi.md
Comment on lines +1049 to +1051
opt-in with a best-effort reaper or is gated on a stronger macOS reaper first.
[future-possibilities]: #future-possibilities

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 28f23d5 — added the missing # Future possibilities header above the anchor so it resolves.

Comment thread text/0000-host-abi.md
Comment on lines +1052 to +1055
- **Resource-typed caller-context.** Replace the payload-carried `principal`
field with a host-owned resource handle exposed via accessor methods. This
removes a cross-cutting record type from `astrid:core` entirely, letting
the host evolve principal representation without any WIT payload change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 28f23d5 — corrected to astrid:sys/host, where caller-context actually lives (the standalone astrid:core package was dropped).

Component Model form for the fs capability-gating example (astrid:fs/host.read-file, not the legacy astrid_fs_read symbol); clarify that astrid-bus schemas are WIT-defined but not linker-enforced (not 'not WIT-typed'); add the missing '# Future possibilities' section header so its anchor resolves; fix a stale 'astrid:core' package reference to 'astrid:sys/host' where caller-context actually lives. The astrid-hook-trigger export name was left as-is: it matches the canonical astrid:guest WIT ([email protected]).
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.

2 participants