Skip to content

feat(sdk)!: migrate to per-domain WIT + wasm32-unknown-unknown - #44

Merged
joshuajbouw merged 9 commits into
mainfrom
feat/per-domain-wit
May 25, 2026
Merged

feat(sdk)!: migrate to per-domain WIT + wasm32-unknown-unknown#44
joshuajbouw merged 9 commits into
mainfrom
feat/per-domain-wit

Conversation

@joshuajbouw

Copy link
Copy Markdown
Member

Summary

Migrate astrid-sdk + astrid-sys to the per-domain WIT host ABI introduced in unicity-astrid/astrid#752, and make wasm32-unknown-unknown the canonical build target.

Changes

astrid-sys — bindings + entropy backend

  • Per-domain WIT staging in build.rs: copies each host/<pkg>@<ver>.wit into wit-staging/deps/astrid-<pkg>/ so wit_bindgen::generate! can resolve the layout.
  • Synthetic capsule world supplied inline (no on-disk world file) — imports every host package and includes every guest export world.
  • __getrandom_v03_custom extern routes getrandom 0.4 entropy through astrid:sys/host.random-bytes, gated on target_arch = "wasm32" AND getrandom_backend = "custom" (the rustflag every capsule's .cargo/config.toml sets).
  • Drops the vendored astrid-capsule.wit — contract lives in the contracts/ submodule, fully Astrid-owned (astrid:* only, no wasi:*).

astrid-sdk — typed wrappers + panic hook

  • Every domain module (fs, ipc, net, process, kv, sys, time, http, approval, elicit, identity, uplink) ported to the new typed-error-code host ABI and resource-handle returns.
  • Resource handles (Subscription, ProcessHandle, HttpStream, TcpStream, UnixListener) carry Drop semantics — closing the handle is automatic on scope exit.
  • install_panic_handler() sets a process-wide panic::set_hook (once-only) that routes Rust panics through astrid:sys/host.log at error level so kernel-side audit captures them — astrid-sdk-macros calls this at the entry of every Guest export method (run, astrid-hook-trigger, astrid-install, astrid-upgrade).
  • astrid_sdk::time::monotonic() exposes the audited host clock — capsules MUST use this instead of std::time::Instant::now() (which panics on wasm32-unknown-unknown).

Build defaults

  • default-features = false on chrono / uuid workspace deps; uuid uses rng-getrandom so it picks up the SDK-provided custom backend instead of pulling wasm-bindgen.
  • [patch.crates-io] points astrid-types at ../core/crates/astrid-types for in-tree builds.

Pairs with

  • unicity-astrid/astrid#752 — kernel-side per-domain WIT migration
  • All unicity-astrid/capsule-* PRs landing the feat/per-domain-wit branch

Test Plan

  • cargo build --workspace clean
  • cargo clippy --workspace --all-features -- -D warnings clean
  • All 17 deployed capsules build against this SDK on wasm32-unknown-unknown
  • End-to-end LLM round-trip verified: astrid run "say hi" → real LM Studio response

Checkpoint commit. Set up the foundation so a follow-up pass can finish
the wrapper migration:

contracts/ submodule
- Bumped from 30c6720 (users@1.0.0) to 324d4ab (per-domain + astrid:io).

astrid-sys
- Replaced single astrid-capsule.wit + monolithic 'capsule' world with
  a synthetic inline world that imports every per-domain host package
  and includes all four guest export worlds (interceptor, background,
  installable, upgradable).
- Added build.rs that stages contracts/host/*.wit into
  wit-staging/deps/astrid-<pkg>/ for wit-bindgen.
- Dropped serde derives from additional_derives — generated Resource
  types own kernel-side handles via Drop and can't be round-tripped
  through serde.
- Compiles clean for wasm32-wasip2.

astrid-sdk
- Includes the file split from local commit 5090ebf (lib.rs broken
  into elicit/http/identity/interceptors/ipc/kv/process per-file
  modules; request_response helper added to ipc).
- lib.rs imports rewritten for the per-domain layout:
  astrid_sys::astrid::<domain>::host as wit_<domain>, plus
  astrid_sys::astrid::io::{error, poll, streams} for the foundation
  primitives.

NOT yet done (handed off to follow-up pass):
- ~124 compile errors across the wrappers:
  - map_err(SysError::HostError) — host fns now return per-domain
    ErrorCode enums, not String. Needs a host_err helper or
    From<ErrorCode> impls.
  - wit_types::* references — types moved into per-domain modules.
  - Resource-backed handles (Subscription, FileHandle, TcpStream,
    ProcessHandle, HttpStream) — should become Drop-managed typed
    handles replacing the u64 ID pattern.
  - Record field renames (FileStat.mtime -> modified, is_dir -> kind
    on the new FileKind enum, IdentityCreateUserResponse shape).
- Macros (astrid-sdk-macros) — Guest trait now combines four worlds.
- examples/test-capsule update against new bindings.
- CHANGELOG entry.

Refs: core PR #752 (kernel-side per-domain migration).
The kernel split the monolithic astrid:capsule@0.1.0 world into
per-domain frozen packages at @1.0.0 (PR #752). This rewrites every
SDK wrapper module against the new contracts so capsule authors can
target the per-domain ABI without losing the existing ergonomic
surface.

Highlights:

- Add host_err<E: Debug>(e) helper that converts any per-domain
  ErrorCode variant into SysError::HostError(format!("{e:?}")), keeping
  the unified SysError public type stable while the per-domain typed
  errors flow through cleanly.

- Resource-backed handles. Subscription, File, Process, TcpStream,
  TcpListener, UnixListener, UdpSocket, and HttpStream are now RAII
  wrappers around wit-bindgen-generated resource types. Drop releases
  the kernel-side resource — no manual unsubscribe / close calls
  required on the happy path.

- Typed value-object adjustments. fs::Metadata exposes typed FileType
  + SystemTime accessors (Datetime conversion); process::Output carries
  structured ExitInfo (exit-code vs signal); ipc::Message carries typed
  PrincipalAttribution (verified / claimed / system) so sensitive-action
  capsules branch on the variant rather than parsing a string.

- New API surface tracking the contract expansion: process::Command
  builder (env / cwd / stdin), net::bind_tcp + TcpListener for inbound
  TCP, net::udp_bind + UdpSocket, net::lookup_host, fs::create_dir_all
  / copy / rename / canonicalize / read_link / hard_link /
  symlink_metadata, kv::list_keys_page + kv::cas, time::sleep /
  monotonic, runtime::random_bytes, approval::Decision +
  request_decision, uplink::Profile.

- hooks::trigger removed (no longer in the host ABI); interceptors::poll
  removed (events flow via astrid-hook-trigger). interceptors::bindings()
  remains for enumeration.

- examples/test-capsule updated for the wit_events! per-interface
  module emission (TestEvent now lives in events::TestEvent).

- Remove orphan astrid-sdk/src/types/sdk_types.rs (unreferenced).

- uuid declared as a real dependency (was implicitly referenced by
  request_response).

Workspace cargo build --target wasm32-wasip2 is clean.
cargo test --workspace passes (66/66 tests). examples/test-capsule
builds to a valid wasi-p2 component. No literal TODO / FIXME /
unimplemented in committed code.

CHANGELOG ## [Unreleased] documents the breaking changes per the
SDK migration guide.
…ract

Lands the sdk-rust side of the wasi-elimination effort (core side
landed as astrid#752, kernel exposes zero wasi:* in the linker).

astrid-types bump
- Workspace dep moves from "0.4.0" to "0.6" so the local-path
  [patch.crates-io] in capsules resolves against
  core/crates/astrid-types (which now ships at 0.6.0 and is decoupled
  from astrid-core per the core PR).
- Workspace [patch.crates-io] points astrid-types at the in-tree core
  path so SDK + capsule builds pick it up without waiting on a
  crates.io publish.

uuid features
- astrid-sdk's uuid dep gains 'rng-getrandom'. Default features pull
  a 'js' RNG on wasm32-unknown-unknown (via wasm-bindgen).
  rng-getrandom routes v4 generation through getrandom — satisfied
  by astrid-sys's __getrandom_v03_custom backend that calls
  astrid:sys.random-bytes.

chrono
- Workspace dep moves to default-features=false, features=['serde'].
  Strips the 'clock' feature that would pull wasm-bindgen on
  wasm32-unknown-unknown.

astrid-sdk kernel re-export drop
- astrid_sdk::types no longer re-exports astrid_types::kernel — those
  types moved to astrid_core::kernel_api per the core decoupling.
  Capsules don't need kernel-management types; they use ipc + llm
  types only.

test-capsule
- Marked wasm32-unknown-unknown in the workspace exclude comment.

Verified end-to-end: 17 Astrid capsules (agents, cli, context-engine,
fs, hook-bridge, http, identity, memory, openai-compat,
prompt-builder, react, registry, router, session, shell, skills,
system) build clean on wasm32-unknown-unknown, install via
astrid-build's component-wrap pipeline, daemon boots with all 17
loaded ready, every component's WIT import list shows astrid:* only
(zero wasi:* anywhere).
On wasm32-unknown-unknown, the default panic strategy is abort —
without a hook, panics produce an opaque wasm trap with a numbered
backtrace and the message is lost entirely. The kernel sees only
'WASM background loop failed' with no actionable cause; per-capsule
logs show no 'panic at src/lib.rs:42' line.

astrid_sdk::install_panic_handler installs a std::panic hook that
formats the panic location + payload via astrid_sdk::log::error
before the wasm process traps. Guarded by std::sync::Once so it's
idempotent across the four Guest export entry points
(astrid_hook_trigger, run, astrid_install, astrid_upgrade).

The #[capsule] proc macro now emits a call to install_panic_handler
on every Guest export entry. Capsule authors don't need to do
anything; opening a capsule's per-capsule log file after a trap now
shows the panic message directly.

Surfaced the std::time::Instant::now() panic
('time not implemented on this platform') in three capsules
(context-engine, prompt-builder, registry) within a minute of the
panic hook landing — those are now fixed in their respective repos.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request transitions the Astrid SDK to a per-domain WIT host ABI, replacing opaque handles with resource-backed RAII types for filesystem, networking, and IPC operations. It also introduces new modules for HTTP streaming, identity resolution, and sandboxed process spawning. Feedback points out a build failure caused by an invalid getrandom dependency version, a runtime panic risk from using std::thread::sleep on the WASM target, and a lint violation involving the use of .expect() in the KV module.

Comment thread astrid-sys/Cargo.toml
Comment thread astrid-sdk/src/net.rs Outdated
Comment thread astrid-sdk/src/kv.rs Outdated
- Drop `[patch.crates-io] astrid-types = path` from the workspace
  Cargo.toml. The patch was a dev-loop convenience for in-tree
  polyrepo builds against `core/crates/astrid-types` and breaks CI
  (the sibling path doesn't exist on a standalone sdk-rust
  checkout). Pulls `astrid-types = "0.6"` from crates.io instead.

- Delete `scripts/sync-host-wit.sh` + its CI step. The script
  mirrored the legacy bundled `contracts/host/astrid-capsule.wit`
  into `astrid-sys/wit/`; per-domain split removed both endpoints.
  The new flow stages WIT at build time from contracts/host/<pkg>@<ver>.wit
  via astrid-sys/build.rs.

- Refresh astrid-sdk/wit/astrid-contracts.wit from the canonical
  contracts/interfaces/ (sync-contracts-wit.sh).
Three dead aliases left over from earlier renames, with zero
in-tree callers (capsules + kernel verified). Per the
'no back-compat shims' rule:

- TcpStream::set_ttl / TcpStream::ttl — pre-migration names
  for set_hop_limit / hop_limit. The kernel-side TcpStream
  host impl calls std::net::TcpStream::set_ttl/ttl (stdlib),
  not the SDK alias.

- Output::exit_code() — flattening accessor that returned -1
  for signal exits. New code branches on the typed ExitInfo
  variant directly.
Two findings from Gemini review on PR #44:

- TcpStream::recv's 50ms poll-interval used std::thread::sleep,
  which panics on wasm32-unknown-unknown with 'time not
  implemented on this platform' the moment a guest stream actually
  blocks. Route through crate::time::sleep (audited host clock).
  Same panic class as the std::time::Instant::now() ones already
  fixed in the capsule layer.

- parse_versioned used .expect() on .remove("data") under a
  match guard that already proves the field is present. Crate
  lint #![deny(clippy::unwrap_used)] forbids expect/unwrap in
  release paths regardless of local safety. Replaced with
  .ok_or_else(...)? — the unreachable error variant is a no-op
  in practice and satisfies the lint.
joshuajbouw added a commit to astrid-runtime/astrid that referenced this pull request May 25, 2026
## Linked Issue

Closes #751.

## Summary

Kernel-side migration to the per-domain WIT host ABI. The legacy bundled
`astrid:capsule@0.1.0` world is replaced by typed-func dispatch over the
split per-domain packages (`astrid:fs@1.0.0`, `astrid:ipc@1.0.0`,
`astrid:net@1.0.0`, `astrid:io@1.0.0`, …). Every host call now routes
through typed `error-code` variants, the wasmtime `ResourceTable` (no
parallel `HashMap` storage), audit envelopes per domain, and
cancellation-token races on every blocking path. **No `wasi:*`
interfaces are exposed to capsules anywhere** — readiness multiplexing
and byte streams are Astrid-owned (`astrid:io/poll`,
`astrid:io/streams`), so the hermit-rs unikernel target stays viable on
the same WIT contract.

The migration is paired with `astrid-runtime/sdk-rust#44` and the 18
`unicity-astrid/capsule-*#feat/per-domain-wit` PRs that flip every
capsule to `wasm32-unknown-unknown` against this kernel. End-to-end LLM
round-trip verified through the pure-astrid stack: `astrid run "say hi"`
→ real LM Studio response.

## Changes

### Foundation

- **WIT submodule.** `wit/` is now a submodule of `unicity-astrid/wit`.
`build.rs` in `astrid-capsule` stages the per-domain layout under
`wit-staging/deps/astrid-<pkg>/` for `wasmtime::component::bindgen!`. CI
workflows check the submodule out recursively.
- **bindings.rs.** Single `bindgen!` over an inline `kernel` world that
imports every host package — one generated module keeps the type
universe deduplicated. `imports: { "astrid:io/streams": trappable }` +
`trappable_error_type` lowers stream-error to wasmtime-wasi-io's runtime
enum so the streams impl can delegate cleanly.
- **No wasi:* linker registration.** Both `engine/wasm/mod.rs` load
paths and `astrid-hooks/handler/wasm.rs` removed the
`wasmtime_wasi::p2::add_to_linker_sync` call. Capsules see exactly what
the per-domain WIT describes and nothing more. `configure_kernel_linker`
is the single source of truth shared between the load path and the
lifecycle-hook path.
- **`astrid-build` target-agnostic.** Drops the hardcoded `--target
wasm32-wasip2`; reads whatever the capsule's `.cargo/config.toml`
selects, probes both wasm targets when locating the artifact, and runs
`wit_component::ComponentEncoder` over `wasm32-unknown-unknown` outputs
to wrap them into a Component Model component in place.
- **wasmtime 43 → 45.** Closes RUSTSEC-2026-0149 (`wasmtime-wasi 43.0.2`
`path_open(TRUNCATE)` `FilePerms::WRITE` bypass). No call-site changes
required; the WasiCtx / WasiView / DynPollable / DynInputStream /
DynOutputStream / IoError types we depend on are stable across the 43→45
window.

### Per-domain host impls

Real implementations with typed errors + audit envelope + cancel-token
races:

| Package | Status |
|---|---|
| `astrid:kv` | full —
`kv-get/set/delete/list-keys/list-keys-page/clear-prefix/cas` (atomic
`compare_and_swap` plumbed through `KvStore` / `ScopedKvStore` in this
branch) |
| `astrid:sys` | full —
`get-config/get-caller/log/signal-ready/clock-ms/clock-monotonic-ns/sleep-ns/random-bytes/check-capsule-capability`.
trigger-hook removed (moved to capsule bus); audit-logged |
| `astrid:uplink` | full — `uplink-register/uplink-send` with typed
`UplinkProfile` |
| `astrid:approval` | full — `ApprovalResponse` carries the typed
`ApprovalDecision` enum; allowance hits map to `Allowance` |
| `astrid:elicit` | full — typed `ElicitType` enum, `ElicitResponse`
variant |
| `astrid:identity` | full — typed errors (`UserNotFound`,
`LinkNotFound`, `AlreadyLinked`, `StoreUnavailable`) |
| `astrid:http` | `http-request` full (SSRF airlock + safe DNS resolver
preserved); `http-stream.read-chunk`, `status`, `headers`, `close` real
|
| `astrid:io/poll` | full — Astrid-owned with 256-pollable cap, audit,
cancel-token race on `block` |
| `astrid:io/error` | full — downcastable error resource
(`to-debug-string`) |
| `astrid:io/streams` | full read/write/skip/check-write/flush/splice
via wasi-sync delegation; cancel + audit + per-call bytes accounting
wraps every op |
| `astrid:net` | `bind-unix`, `UnixListener.{accept, poll-accept}`
(session-token handshake), `connect-tcp` (SSRF airlock), `lookup-host`
(airlocked DNS), `TcpStream` byte methods + 20 socket options |
| `astrid:ipc` | `publish`, `publish-as`, `subscribe` returning
`Resource<Subscription>`, `Subscription.{poll, recv}` with cancel +
per-message principal context install |
| `astrid:fs` | path-based ops on the new `FileStat` shape
(kind/mode/modified/created/accessed) including `fs-mkdir-all` |
| `astrid:process` | `spawn` (sync), `spawn-background` returning
`Resource<ProcessHandle>`, `ProcessHandle.{read-logs, wait, kill,
os-pid, signal}`. Marked desktop-only per the WIT |

Stubbed for documented follow-ups (return `Unknown("port pending")` or
`CapabilityDenied` — no panics):

- `bind-tcp` / `TcpListener` (inbound capsule-hosted TCP)
- `udp-bind` / `UdpSocket` (datagram I/O)
- `tcp-stream.{read-stream, write-stream}` + `http-stream.body-stream`
(wasmtime-wasi-io InputStream/OutputStream adapter — pairs with
capsule-hosted TCP work)
- `subscribe-*` pollables on every resource (the pollable adapter lands
as one commit covering everything)
- `fs-open` + `FileHandle` resource (positional pread/pwrite, fsync,
set-len)
- `fs-stat-symlink`, `fs-append`, `fs-copy`, `fs-rename`,
`fs-remove-dir-all`, `fs-canonicalize`, `fs-read-link`, `fs-hard-link`
- **`ProcessHandle.{write-stdin, close-stdin, wait-with-output}` —
unblocked in a follow-up to support a capsule-level MCP server.** MCP
servers communicate via JSON-RPC over stdio; for a `capsule-mcp` to
spawn an MCP server subprocess and drive it, the kernel needs stored
stdin pipes (`Stdio::piped()` + `ManagedProcess` field), a background
write task, and an atomic-drain `wait-with-output`. Out of scope for
#752 itself.

### `HostState` shape

Removed (`ResourceTable` is canonical storage now):
- `active_streams: HashMap<u64, NetStream>` + `next_stream_id`
- `subscriptions: HashMap<u64, EventReceiver>` + `next_subscription_id`
- `background_processes: HashMap<u64, ManagedProcess>` +
`next_process_id`

Added (O(1) quota-gate counters maintained on insert/drop, per Gemini
review):
- `net_stream_count: usize`
- `subscription_count: usize`
- `process_count_total: usize`
- `process_count_by_principal: HashMap<PrincipalId, usize>`

Engine init, lifecycle init, `test_fixtures`, and the hook handler all
updated.

### Audit channels

- `astrid.audit.fs` — path-based fs ops
- `astrid.audit.io` — stream + pollable ops
(read/write/splice/block/poll)
- `astrid.audit.net` — TCP / Unix socket ops
- `astrid.audit.ipc` — publish/subscribe/poll/recv
- `astrid.audit.http` — request / stream ops
- `astrid.audit.process` — spawn / spawn-background

Every event carries `capsule_id`, `principal` (effective
per-invocation), op name, and a domain-appropriate payload.

### Why Astrid-owned and not `wasi:io`

The wasmtime-wasi `Host` impl skips four things Astrid considers
non-negotiable for security:

- **Cancellation.** `pollable.block()` and `stream.blocking-read()`
strand the host task on a future that may never complete when the
capsule unloads. Astrid's wrappers race against `cancel_token`.
- **Audit.** Every read/write/poll/splice is invisible to the audit log.
Astrid emits per-call events.
- **Per-principal accounting.** No quota dial on pollable / stream
handles, no rate limit on poll-loop spam.
- **Uniformity.** A carve-out for "foundation types" undermines
defense-in-depth.

Owning the namespace also matters for the hermit-rs unikernel target:
contract stays stable, host-side impl swaps for native unikernel wait/io
primitives.

### Review fixups landed in this PR

3-agent review + Gemini review surfaced the following, all addressed
in-branch:

- `ipc::recv` mixed-principal batches truncated at the first publisher
boundary (`truncate_to_homogeneous_principal`).
- `TcpStream::write` propagates peer-disconnect IO kinds as
`ErrorCode::ConnectionReset` instead of swallowing them as `Ok(())`.
- `TcpStream::read` cancellation returns `Closed` (not `Pending`).
- `spawn_background` registers the spawned PID in `ProcessTracker`; the
drop path unregisters.
- `Subscription` resource handle stays valid across multiple `recv`
calls (`EventReceiver` behind `Arc<Mutex<...>>`).
- `read_file` re-checks payload size post-read for `TooLarge`
(eliminates pre-stat TOCTOU).
- `ProcessHandle::wait` uses `spawn_blocking(child.wait)` raced against
`tokio::time::timeout`.
- `unix_listener::accept` 100ms back-off on credential failure.
- All `count_*` resource-table iteration replaced with O(1) counter
fields on `HostState`.
- HTTP per-chunk timeout extracted to `HTTP_STREAM_READ_TIMEOUT`.
- 21 new unit tests covering the fixes.
- Atomic `kv_cas` through `KvStore` / `ScopedKvStore` in
`astrid-storage` (was originally deferred; landed in this branch).

## Test Plan

### Automated

- [x] `cargo test --workspace` passes
- [x] `cargo build --workspace` clean
- [x] `cargo clippy --workspace --all-features -- -D warnings` clean
- [x] `cargo test -p astrid-capsule --lib` — 272 passed, 0 failed
(includes 21 new regression tests)
- [x] wasmtime 45.0.0 bump verified — all tests pass, no API breakage at
our call sites

### Manual

- [x] End-to-end LLM round-trip: `astrid run "say hi in one short
sentence"` returns a real LM Studio response, full pure-astrid stack
(router → session → react → openai-compat → http → LM Studio), zero
`wasi:*` imports anywhere, daemon survives `astrid restart` cycles.

## Out of scope (separate PRs)

- Stream-half adapters (`read-stream` / `write-stream` / `body-stream` /
`FileHandle`) — paired with capsule-hosted TCP server work
- Pollable wiring for `subscribe-*` methods
- `bind-tcp`, `UdpSocket` (capsule-hosted networking)
- **MCP-driven stdio**: `ProcessHandle.{write-stdin, close-stdin,
wait-with-output}` real impls + `ManagedProcess` stdin-pipe storage +
write-task plumbing. Required for `capsule-mcp` to drive
JSON-RPC-over-stdio MCP servers as subprocesses.
- Restoring `ipc_tests.rs` against the new Subscription-resource shape
…ts sync regex

Three coordinated fixes to unblock sdk-rust 0.7.0 publish.

kv-cas cascade (wit#10)
- astrid_sdk::kv::cas keeps Result<bool, SysError> public surface
  for capsule code, translates the new WIT-level
  Err(CasMismatch) → Ok(false) at the SDK boundary.
- contracts submodule bumped to wit/main (post wit#10 merge).

Workspace bump 0.6.1 → 0.7.0
- Crates-io's astrid-types 0.6.0 was never published (skipped to
  0.7.0 in core); the workspace dep  failed
  to resolve. Bump to "0.7" picks up the now-published 0.7.0.
- All three workspace crates (astrid-sdk, astrid-sdk-macros,
  astrid-sys) bump to 0.7.0 in lockstep to track the core release
  line.

sync-contracts-wit.sh regex
- The canonical wit interfaces moved from the  namespace
  to  at some point. The strip-and-rewrite sed
  patterns still matched  only, leaving 17 raw
   declarations in the bundled
  output that wit-parser rejects ("expected '{', found ';'").
- Generalised the pattern to  so both namespaces
  are stripped. Switched away from  because
  BSD sed (macOS) errors on alternation in -E mode. Bundled file
  regenerated (1249 lines, parses clean).

Verified: cargo test --workspace passes (multiple suites), cargo
clippy --workspace --all-features -- -D warnings clean.
Every job that runs cargo check / clippy / test / msrv against
the workspace needs the contracts/ submodule (unicity-astrid/wit)
on disk because astrid-sys's build.rs reads from contracts/host
to stage per-domain WIT files. Without recursive checkout the
build script panics:

  read contracts/host: Os { code: 2, kind: NotFound, message:
    "No such file or directory" }

The wit-sync job already had the right config; mirroring it
across check, clippy, test (matrix), and msrv. Format + audit
don't compile so they don't need the submodule.
@joshuajbouw
joshuajbouw merged commit dcd154d into main May 25, 2026
8 checks passed
@joshuajbouw
joshuajbouw deleted the feat/per-domain-wit branch May 25, 2026 21:58
joshuajbouw added a commit to astrid-runtime/sdk-js that referenced this pull request May 25, 2026
## Summary

Migrate `@astrid-os/sdk` (JS/TS) to the per-domain WIT host ABI
introduced in `astrid-runtime/astrid#752`, paired with
`astrid-runtime/sdk-rust#44`. Keeps the JS and Rust capsule contracts in
lockstep so a capsule author sees the same surface and the same type
names across both languages.

## Changes

### Per-domain bindings + wrappers

Every domain module (`fs`, `ipc`, `net`, `process`, `kv`, `sys`, `time`,
`http`, `approval`, `elicit`, `identity`, `uplink`) ported to the new
typed-`error-code` host ABI:

- WIT bindings (`wit-imports.d.ts`) regenerated against the per-domain
packages — `astrid:fs/host@1.0.0`, `astrid:ipc/host@1.0.0`,
`astrid:net/host@1.0.0`, `astrid:io/poll@1.0.0`,
`astrid:io/streams@1.0.0`, `astrid:io/error@1.0.0`, etc.
- Resource handles (`UnixListener`, `TcpListener`, `TcpStream`,
`UdpSocket`, `Subscription`, `BackgroundProcessHandle`, `HttpStream`)
are Component Model resources with `Symbol.dispose` for `using`
scope-bound cleanup.
- Typed `SysError` mirrors the Rust SDK's `SysError::HostError(String)`
shape; per-domain typed errors get formatted via the same Debug-style
conversion at the SDK boundary.

### WIT submodule + mirror

- `contracts/` is the `unicity-astrid/wit` submodule (shared with
sdk-rust).
- `scripts/sync-contracts-wit.sh` mirrors `contracts/interfaces/*.wit` →
`packages/astrid-sdk/wit-contracts/astrid-contracts.wit` (the
published-package physical-location requirement). CI gate runs
`--check`.

### Post-migration cleanups (mirroring sdk-rust)

- Drop `TcpStream.setTtl` / `TcpStream.ttl` back-compat aliases —
pre-migration names for `setHopLimit` / `hopLimit` with zero callers in
capsule code. Parallel to `unicity-astrid/sdk-rust@f90012c`.
- Route `TcpStream.recv`'s 50ms poll-interval through the audited
`astrid:sys/host.sleep-ns` (via `time.sleepMs`) instead of busy-spinning
on `clockMs`. The kernel can now cancel the wait when the capsule
unloads and account for the wait in audit. Parallel to
`unicity-astrid/sdk-rust@81c85c1` (`std::thread::sleep` →
`crate::time::sleep`).
- Refresh `astrid-contracts.wit` from canonical (same drift sdk-rust
corrected earlier in the branch).

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side per-domain WIT migration
- `astrid-runtime/sdk-rust#44` — Rust SDK migration
- All `unicity-astrid/capsule-*#feat/per-domain-wit` PRs (capsule layer)

## Test Plan

- [x] `npm run build` clean across the workspace (`packages/astrid-sdk`
+ `packages/astrid-build` + `examples/test-capsule`)
- [x] `examples/test-capsule` componentizes via `componentize-js`
against the new bindings — 12.44 MB / 154 host imports, all `astrid:*`
(zero `wasi:*` from capsule POV)
- [x] `scripts/sync-contracts-wit.sh --check` passes
- [ ] End-to-end smoke once an actual JS capsule lands against this SDK
(Rust-side `astrid run "say hi"` already verified for the cross-SDK
kernel)
joshuajbouw added a commit that referenced this pull request May 25, 2026
## Linked Issue

Closes #46

## Summary

Roll `[Unreleased]` into `[0.7.0]` to pair with the merged
`unicity-astrid/astrid` 0.7.0 release. Workspace was already bumped
0.6.1 → 0.7.0 in #44 (per-domain WIT migration); this PR is the
documentation-side companion.

## Changes

- `CHANGELOG.md`: `[Unreleased]` → `[0.7.0] - 2026-05-26`. No content
changes — the section already has the per-domain WIT migration roll-up
(Breaking + Added + Changed + Fixed) from when #44 merged.
- No `Cargo.toml` changes — workspace version + internal deps already at
0.7.0.

## Test Plan

- [x] `cargo check --workspace` clean

## Manual

- [ ] Tag `v0.7.0` after merge
- [ ] `cargo workspaces publish --from-git` publishes astrid-sys /
astrid-sdk-macros / astrid-sdk in dep order

## Checklist

- [x] Linked to an issue
- [x] CHANGELOG.md updated
joshuajbouw added a commit to unicity-aos/capsule-agents that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-cli that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-context-engine that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-fs that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-hook-bridge that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-http that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-identity that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-memory that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-openai that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-openai-compat that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-prompt-builder that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-react that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-registry that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-router that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-session that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-shell that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-skills that referenced this pull request May 26, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/capsule-system that referenced this pull request May 29, 2026
## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `astrid-runtime/astrid#752`
(paired with `astrid-runtime/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `astrid-runtime/astrid#752` — kernel-side migration
- `astrid-runtime/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`
joshuajbouw added a commit to unicity-aos/aos-ce that referenced this pull request Jul 13, 2026
* feat: native OpenAI LLM provider capsule

Talks directly to OpenAI's Chat Completions API with support for:
- Strict function calling (strict: true on tool definitions)
- Reasoning effort for o-series models (low/medium/high)
- Service tier routing (auto/default/flex/priority)
- max_completion_tokens (OpenAI's preferred field)
- Parallel tool calls

Separate from openai-compat which handles generic OpenAI-compatible
providers. This capsule is OpenAI-specific.

* feat: native OpenAI provider — Responses API, model registry, repo setup (#1)

## Summary

Complete native OpenAI LLM provider capsule using the **Responses API**
(`POST /v1/responses`), not the legacy Chat Completions endpoint.

## Changes

- **Responses API**: `input` + `instructions` schema, named SSE events
(`event: response.output_text.delta`), `reasoning.effort` nested object
- **Model registry**: Built-in lookup table for all current OpenAI
models (GPT-5.4/mini/nano/pro, GPT-5.3 Codex, GPT-5.2, GPT-4.1 series,
o-series, GPT-4o legacy). Selecting a model auto-resolves context
window, max output tokens, vision/tools/reasoning support. Env vars
override.
- **Strict function calling**: `strict: true` on all tool definitions
- **Reasoning-aware temperature**: Temperature only sent when reasoning
effort is `none` (GPT-5.4 constraint). Older reasoning models skip
temperature entirely.
- **Net capabilities**: Locked to `api.openai.com` hostname only
- **Repo setup**: README, dual MIT/Apache-2.0 licenses, GitHub Actions
release workflow with BLAKE3 hashes

## Test plan

- [ ] Build: `cargo build --target wasm32-wasip1 --release`
- [ ] Install and test with OpenAI API key against GPT-5.4
- [ ] Verify model registry resolves context_window correctly for each
model family
- [ ] Verify reasoning_effort only sent for reasoning models
- [ ] Verify temperature skipped when reasoning effort is non-none

* chore: add CI workflow (#2)

Add fmt + clippy + WASM build checks on PRs and main pushes. Matches
other capsule repos.

* feat: initial Telegram Bot uplink capsule

Bridges the Telegram Bot API to the Astrid kernel IPC bus.

Features:
- Streaming responses with throttled message editing
- Markdown to Telegram HTML conversion
- Approval/elicitation inline keyboards
- Session management with KV persistence
- Access control via user ID allowlist
- Bot commands: /start, /help, /reset, /cancel

* fix: align with astrid-sdk 0.5.3 API

- Use Response.json()/text() instead of .status/.body fields
- Add description parameter to elicit::secret/text_with_default
- Fix formatting (rustfmt --edition 2024)
- Suppress expected dead_code warnings on deserialized fields

* fix: target wasm32-wasip1 to match astrid build system

Add .cargo/config.toml and rust-toolchain.toml matching other capsules.

* fix: remove install hook to avoid double-prompting

Capsule.toml [env] section already handles prompting for bot_token and
allowed_user_ids during install. The #[astrid::install] hook was
duplicating the same prompts.

* fix: use wildcard net capability to match kernel security gate

The kernel's Extism-based security gate doesn't support URL-pattern
net capabilities like 'https://api.telegram.org/*'. Use '*' wildcard
matching the pattern used by capsule-openai-compat.

* fix: add net capability to component section

The kernel's Extism-based security gate checks per-component capabilities,
not the top-level [capabilities] section. Without capabilities on the
[[component]], network access is denied at runtime.

* fix: unwrap SDK HTTP response envelope before parsing Telegram JSON

http::send() returns a JSON envelope {status, headers, body} where body
is the actual HTTP response as a string. Parse the envelope first, then
deserialize the Telegram API response from the body field.

* docs: fix WASM target in README (wasip1, not wasip2)

* fix: multi-user approval/elicitation routing (#11)

* fix: use session_id from payload for multi-user approval/elicitation routing

Remove find_chat_for_event which silently dropped approval and elicitation
events when multiple users had active turns. Instead, extract session_id
directly from the IPC payload and resolve the target chat via session_to_chat.
Falls back to single-active-turn heuristic only when no session_id is present.

Closes #1

* fix: drop events with unresolvable session_id instead of misrouting

Extract resolve_chat_from_payload helper to deduplicate chat resolution
logic between approval_required and elicit_request handlers.

The previous or_else fallback would use the single-active-turn heuristic
even when session_id was present but unresolvable (stale or unknown),
which could misroute events. Now we only fall back to the heuristic when
session_id is truly absent from the payload; if it is present but cannot
be resolved, the event is dropped.

* fix: propagate uplink::register error instead of silently discarding it (#15)

Closes #2

* fix: exponential backoff on Telegram API failures (#12)

* fix: add exponential backoff on persistent Telegram API poll failures

Replace the fixed 2-second sleep on Telegram poll errors with
exponential backoff (2s, 4s, 8s, ... up to 60s). The counter resets
on every successful poll, preventing runaway retry storms during
prolonged outages.

Closes #3

* fix: use non-blocking backoff to avoid stalling IPC during Telegram errors

Replace std::thread::sleep backoff with a next_poll_at timestamp so the
main loop continues processing IPC events while Telegram polling is
deferred. Adds a 50ms tick at the end of the loop to prevent
busy-spinning when polls are skipped.

* fix: TTL-based cleanup for turns and pending_approvals memory leak (#17)

Add created_at and last_activity timestamps to TurnState and created_at
to PendingApproval. Introduce TURN_TIMEOUT and APPROVAL_TTL constants
(300s each) and a Phase C cleanup pass in the main loop that evicts
stale entries, preventing unbounded HashMap growth.

Expired turns collect their IDs first, then retain removes them, and
finally Telegram API calls notify users — avoiding mutable borrow
conflicts during I/O (per Copilot review feedback). Turn timeout is
based on last_activity (updated on stream deltas and approval events)
so long-running turns with ongoing activity are not prematurely reaped.

Closes #4

* fix: check HTTP status codes and monitor IPC dropped messages (#14)

* fix: check HTTP status codes and monitor IPC dropped messages

Check envelope.status in parse_response before parsing Telegram JSON:
429 returns a clear rate-limit error, >=500 surfaces the server error
with a truncated body, and >=400 attempts to extract the Telegram error
description. Also log a warning when the IPC poll envelope reports
dropped messages so stale responses are visible in logs.

Closes #5, closes #7

* fix: UTF-8 safe truncation and shared unwrap_envelope helper

- Replace &envelope.body[..200] byte-slicing with chars().take(200)
  to avoid panicking on multi-byte UTF-8 boundaries.
- Extract unwrap_envelope() helper that checks HTTP status (429, 5xx,
  4xx) and returns the body string on success.
- Refactor both parse_response and edit_message_text to use the shared
  helper, so edit_message_text now properly checks HTTP status too.

* fix: enum parsing, Capsule.toml bloat, link double-escape, dead code (#18)

- #6: Fix handle_elicitation_request — look for field_type as an object
  with "Enum" key containing array instead of broken string equality
  check; add !buttons.is_empty() guard
- #8: Trim Capsule.toml ipc_publish and ipc_subscribe to only the
  topics the code actually uses (removed 11 unused topic patterns)
- #9: Add html_unescape helper in format.rs; unescape URL before
  re-escaping for href to prevent double-escape; add tests
- #10: Remove unused session_id field from PendingApproval; remove
  dead code in handle_final_response; add unit tests for
  parse_allowed_users, is_user_allowed, new_session_id

* fix: address all findings from second code review (#19)

* fix: address all 18 findings from second code review

High:
- #17: Text elicitations now tracked in pending_elicitations map;
  user text replies routed as elicit_response instead of new turn

Medium:
- #1: getUpdates switched from GET with unencoded query to POST with JSON body
- #4: Elicitation callbacks validated against pending_elicitations state
- #5: KV errors now logged as warnings instead of silently discarded
- #9: Graceful exit after 50 consecutive IPC poll errors
- #14: Callback data truncated to respect Telegram's 64-byte limit
- #16: Unconditional 50ms sleep now only runs during backoff

Low-Medium:
- #11: text_buffer capped at 256KB to prevent WASM OOM
- #7: uplink::register kept (kernel bookkeeping) with comment
- #13: net=[*] kept (kernel rejects URL patterns) with comment

Low:
- #10: Turn timeout uses collect+remove instead of double retain (TOCTOU fix)
- #18: Approval decision text now HTML-escaped
- Pending elicitation TTL cleanup added alongside approvals

* fix: address Copilot review feedback on PR #19

- Approval request_id truncated with floor_char_boundary (UTF-8 safe),
  and pending_approvals keyed by the truncated id so lookups match
- IPC error counter reset on any Ok (including empty polls)
- Text buffer cap enforced strictly: partial append up to MAX_TEXT_BUFFER

* fix: address second Copilot review round

- Elicitation check moved before command parsing so /path replies work
- IPC publish failure re-inserts pending elicitation for retry
- Approval callback uses short token in callback_data but stores full
  request_id in PendingApproval for correct IPC routing
- IPC error counter tracks per full pass, not per handle
- (Tests for new state machine deferred — needs IPC/KV mocking)

* fix: address third Copilot review round

- Approval/elicitation IPC publish failures now re-insert pending state
  and notify user to retry (no more silent loss)
- Approval callback_data uses FNV hash token for long request_ids to
  avoid prefix-collision risk; full request_id stored in PendingApproval
- IPC error counter tracks per full pass (all handles), not per handle
- Elicitation comment fixed to match skip behavior (not truncation)
- Added 4 tests for callback_token: short passthrough, long hashing,
  distinct ids, and 64-byte callback_data fit

* fix: clone request_id before use in elicitation handler

Bind request_id before building payload/topic to avoid potential
ownership confusion (the json! macro borrows, but cloning makes
intent explicit and prevents future refactoring surprises).

* fix: use callback_token for elicitation callback_data too

Elicitation enum options now use the same FNV hash tokenization as
approvals, maximizing space for option values within the 64-byte
callback_data limit. Validation matches against both full id and token.

* fix: address round 6 Copilot feedback

- Log full_request_id (not callback token) in approval TTL cleanup
- Log full_id in elicitation publish failure
- Bump turn last_activity on elicit_request to prevent timeout during input
- Clarify FNV hash comment: not crypto-resistant, sufficient for transient tokens

* fix: hash request_ids containing colons, update callback format docs

Colons in request_ids would break splitn(3, ':') parsing. callback_token
now always hashes ids containing ':'. Updated format comment to reflect
token-based callback_data structure.

* fix: detect and log callback token hash collisions on insert

* refactor: rename APPROVAL_TTL to PENDING_INTERACTION_TTL

Now governs both approvals and elicitations, name should reflect that.

* docs: fix install command and add reinstall/purge notes

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#7)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#13)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#9)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#10)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#10)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#11)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#15)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#10)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#15)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#3)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#14)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#17)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#10)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#12)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#10)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#9)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#5)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* feat: migrate to SDK 0.6.0 (wasmtime Component Model + WIT-driven types) (#10)

## Summary

Migrate to astrid-sdk 0.6.0 targeting wasm32-wasip2 (Component Model).

- Remove `extism-pdk` dependency
- Update `astrid-sdk` to 0.6.0 (from crates.io)
- Migrate IPC APIs: `recv_bytes`/`poll_bytes` → typed `PollResult`
- Migrate `hooks::trigger(&[u8])` → `hooks::trigger(&str)`
- Migrate `log::log("level", msg)` → typed `log::info(msg)` etc.
- Add `[[topic]]` declarations to Capsule.toml where applicable
- Build target: wasm32-wasip2

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#12)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#13)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#11)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#11)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#18)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#16)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#11)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#11)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#10)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#14)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
cargo-like-manifest in the rfcs repo, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs where
applicable.

Other capabilities (sandboxing, fs/net allowlists) preserved verbatim.
No src changes — manifest-only, behaviour unchanged.

## Dependencies

* RFC: `unicity-astrid/rfcs#26` (cargo-like manifest schema).
* Parser: `unicity-astrid/astrid#713` (cargo-like manifest schema parser
in core). The kernel reads both legacy and new forms equivalently during
the migration window.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#12)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
`unicity-astrid/rfcs#26`, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Canonicalise bare-name wit refs to the full `@unicity-astrid/wit/...`
form.
* Drop remaining TODOs onto newly-added canonical WIT refs.

Other capabilities preserved verbatim. No src changes — manifest-only,
behaviour unchanged.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema + restore on_before_prompt_build binding (#15)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
`unicity-astrid/rfcs#26`, parser support in
`unicity-astrid/astrid#713`), and restores the `on_before_prompt_build`
interceptor binding that was lost in a prior refactor.

* `chore: convert to Cargo-like [publish]/[subscribe] schema` — same
conversion as the rest of the capsule fleet.
* `fix(manifest): restore lost handler binding for
on_before_prompt_build` — the new schema does not auto-derive
interceptor bindings from a `subscribe` entry; this commit makes the
binding explicit so the handler keeps firing under the new schema.
* `chore: replace TODO wit refs with canonical @unicity-astrid/wit/...`
— canonical refs.

No src changes. The handler-binding restoration is critical: without it,
memory capsule silently stops participating in the prompt-build hook
chain after the manifest migration.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Manual: `on_before_prompt_build` fires for memory capsule under a
daemon running the cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#17)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
`unicity-astrid/rfcs#26`, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.
* Drop remaining TODOs onto newly-added canonical WIT refs.

Manifest-only, behaviour unchanged. Note: the existing PR #16
(`fix/payload-data-unwrap`) covers a separate src-side fix for the
`Custom { data }` unwrap pattern; this PR is purely manifest.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(manifest): convert to Cargo-like [publish]/[subscribe] schema with canonical wit refs (#13)

## Summary

Converts `Capsule.toml` to the new Cargo-like manifest schema (RFC:
`unicity-astrid/rfcs#26`, parser support in
`unicity-astrid/astrid#713`):

* Move legacy `[capabilities].ipc_publish` / `ipc_subscribe` arrays into
typed `[publish]` and `[subscribe]` tables.
* Replace `"TODO"` wit refs on resolvable topics with the canonical
`@unicity-astrid/wit/<noun>/<record>` path.

Manifest-only, behaviour unchanged. The src-side `Custom { data }`
unwrap fix is covered by the existing PR #11
(`fix/payload-data-unwrap`); this PR is purely manifest and
intentionally drops the duplicate src commit so review stays focused.

## Test plan

- [x] `cargo build --target wasm32-wasip1 --release`
- [x] Capsule loads cleanly under a daemon running the
cargo-like-manifest parser PR.

* chore(deps): bump astrid-sdk to 0.6.1 (#18)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#14)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#13)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#14)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#12)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#12)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#19)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#16)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#17)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#14)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#12)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#12)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#11)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#15)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* chore(deps): bump astrid-sdk to 0.6.1 (#13)

Bumps `astrid-sdk` dependency from 0.6.0 to 0.6.1.

Picks up:
- `ipc::publish_as` / `ipc::publish_json_as` (uplink principal
propagation, sdk-rust #37)
- Canonical-WIT-sourced contracts with per-interface `pub mod <iface>`
namespacing (sdk-rust #39)
- `cargo publish -p astrid-sys` fix (sdk-rust #36)

No source change required — `astrid-sdk` 0.6.1 is API-compatible at
every site this capsule touches.

* feat: initial scaffold of the users capsule

Implements astrid:users@1.0.0 over IPC RPC. Capsule subscribes to
users.v1.<op>.request and publishes users.v1.<op>.response for the
eight operations: resolve, link, unlink, create, links, get, delete,
list. Each request carries a source envelope (channel, user-id,
correlation-id) so multi-tenant uplinks (sphere, discord, telegram)
can route responses back to the originating end-user by correlation.

KV key layout mirrors the legacy kernel astrid-storage::identity
store byte-for-byte (user/{uuid}, link/{platform}/{id},
name/{display_name}) so the future kernel-side cutover
(unicity-astrid/astrid#747) reads existing records unchanged.

Internal layout:
  - lib.rs      — capsule entrypoint + IPC dispatchers
  - types.rs    — domain records (AstridUser, FrontendLink, Source)
  - store.rs    — KV-backed store + Backend substitution seam
  - requests.rs — inbound payload structs (kebab + snake alias)
  - responses.rs — outbound JSON projection (kebab-case)
  - time.rs     — RFC 3339 formatting via Hinnant civil_from_days

36 unit tests cover key validation (path traversal, null bytes),
upsert semantics, cascade delete, name-index last-writer-wins,
request envelope deserialization, and timestamp formatting.

Closes unicity-astrid/astrid#747 (capsule side; the kernel-side
deletions ship in a follow-up once SDK wrappers cut over).

* self-review: clarify storage-shape divergence + tidy list_users projection

- Document that the on-disk JSON shape diverges from the legacy kernel
  store in three places (public_key as list<u8> vs base64; ms-precision
  timestamps vs chrono's us-precision; AstridUser drops the redundant
  principal field). Pre-launch with no records to migrate, the
  divergences are deliberate — value layout follows the WIT contract,
  not the kernel's Rust serialization. README + types.rs module
  docstring carry the explanation; the earlier 'byte-for-byte' claim
  was overstated.

- Replace the misleading filter_map on list_users with a direct map
  via a new user_value helper. user_to_json(Some(u)) always returns
  Some, so the filtering semantics never fired — latent bug if anyone
  later changes user_to_json to drop partially-invalid records.

* fix: drop [patch.crates-io] — use published astrid-sdk 0.6.1

The local-worktree patch was inherited from peer capsules and pinned
astrid-sdk/astrid-sdk-macros/astrid-sys to absolute paths that only
exist on the original author's machine. CI, contributors, and anyone
else cloning the repo hit:

    error: failed to load source for dependency `astrid-sdk`
    Caused by: Unable to update /…/sdk-rust/astrid-sdk

Removing the patch makes Cargo resolve astrid-sdk = "0.6.1" from
crates.io (already the base dep). Verified: cargo build --release
--target wasm32-wasip1 pulls 0.6.1 cleanly; 36/36 unit tests pass;
clippy and fmt are clean.

* feat: implement expanded astrid:users@1.0.0 surface

Catches the capsule up to the merged WIT (unicity-astrid/wit#6):

  - source.uplink (was source.channel) — disambiguates from
    frontend-link.platform.
  - frontend-link.platform_instance for Slack workspaces, IRC
    networks, XMPP servers. KV key is now
    link/{platform}/{instance|_}/{platform_user_id}, with _ as the
    reserved sentinel for None.
  - frontend-link.display_name — platform-side global name at link time.
  - set_display_name + set_public_key topics for mutating AstridUser
    fields without rotating the UUID.
  - cursor/limit pagination on list_users and the two new context
    list topics.
  - Per-context display-name overlay (two-layer identity model):
    ContextIdentity record + five users.v1.context.* topics
    (set/clear/get/list_for_user/list_in_context). KV prefix is
    context/{platform}/{instance|_}/{context_id}/{platform_user_id}.
  - resolve becomes context-aware and returns a layered display name
    in one round-trip (context > link > canonical).
  - Cascade on unlink: drops every context overlay tied to the link.
  - Cascade on delete_user: drops every link and overlay for that user.

src/store.rs grew to 734 lines; store tests moved to src/store_tests.rs
to stay under CI's 1000-line cap. 42 unit tests on the host target
cover: identity CRUD with instance scoping, mutation, layered resolve
fallback chain, context overlay CRUD, two cascade paths, pagination
across list_users / list_context_for_user, sentinel-reserved
validation. Wasm release: 255 KB, fmt + clippy clean on both wasm
and host.

* test: platform scenarios — Discord/Slack/Telegram/Matrix/X/IRC/Mastodon/Email/SMS/Nostr/Passkey/GitHub

Real-shape end-to-end tests against the data models of every platform
from the audit. Each test uses platform-realistic identifiers and
exercises the link → resolve → list-links path plus the
platform's distinguishing characteristic:

  - Discord: 18-digit snowflakes + per-guild nickname layering
  - Slack: workspace-scoped IDs (T/U pairs); same U-id in different
    workspaces resolves to different humans; per-channel context
  - Telegram: int64 user-ids; @username refresh via re-link
  - Matrix: '@alice:server.org' federated IDs with no instance;
    per-room display-name overlays
  - X: numeric stable id, handle change via re-link
  - IRC: per-network scoping ('alice' on libera vs oftc)
  - Mastodon: '@alice@server.social' federated; no instance
  - Email: globally-unique address
  - SMS: E.164 phone numbers
  - Nostr: npub-as-identity with public_key on AstridUser
  - Passkey: credential-id link with public_key on AstridUser
  - GitHub: numeric id stable, login change via re-link

Plus three group-setting cases:

  - One human linked across six platforms; resolve from any returns
    the same AstridUserId.
  - Five-member Discord guild member roster via context.list_in_context,
    paginated, every row resolves to a user.
  - Bot-vs-human attribution via method='bot' audit string.

20 new tests, total now 62. Catches contract-level data-model
mismatches before any uplink consumes the WIT — closes the largest
gap in the earlier 'will this work 100%' caveats list.

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#6)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#15)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#14)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#15)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#13)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#13)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#20)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#17)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#4)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#18)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#15)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#19)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unknown-unknown"` with
`--cfg=getrandom_backend="custom"` rustflag so `getrandom 0.4` picks up
the SDK's `__getrandom_v03_custom` extern (routes entropy through
`astrid:sys/host.random-bytes`).
- `rust-toolchain.toml` — `targets = ["wasm32-unknown-unknown"]`.
- `[patch.crates-io]` — points `astrid-sdk*` / `astrid-sys` /
`astrid-types` at the in-tree workspaces so the polyrepo cross-cuts
resolve.
- Call-site updates for the new typed-`error-code` SDK surface where
touched.

## Pairs with

- `unicity-astrid/astrid#752` — kernel-side migration
- `unicity-astrid/sdk-rust#44` — SDK migration
- All other `unicity-astrid/capsule-*` PRs on `feat/per-domain-wit`

## Test Plan

- [x] `cargo build --target wasm32-unknown-unknown --release` clean
- [x] `cargo clippy --release -- -D warnings` clean
- [x] Loads + runs under the migrated kernel — verified end-to-end via
`astrid run "say hi"`

* feat!: migrate to per-domain WIT + wasm32-unknown-unknown (#14)

## Summary

Migrate this capsule to:

1. The per-domain WIT host ABI introduced in `unicity-astrid/astrid#752`
(paired with `unicity-astrid/sdk-rust#44`).
2. `wasm32-unknown-unknown` as the canonical build target — zero
`wasi:*` imports, every host call routed through audited `astrid:*`
interfaces.

## Changes

- `.cargo/config.toml` — `target = "wasm32-unkn…
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