host: Phase 2 complete — SXTP wire format + Dream bridge, 82/82

lib/host/sxtp.sx implements the host<->subsystem wire format per
applications/sxtp/spec.sx:

- message algebra (request/response/condition/event + status helpers
  ok/created/not-found/forbidden/invalid/fail) as string-keyed dicts;
  verb/status/type stored as symbols (ride the wire bare)
- codec: sxtp/serialize (dict -> text/sx list form, deterministic top-level
  field order, nested messages emitted in their own list form, no :msg leak)
  and sxtp/parse (text/sx -> dict via a deep keyword-token->string normaliser)
- Dream bridge: sxtp/from-dream (HTTP req -> SXTP req, method->verb,
  query->params) and sxtp/to-dream (SXTP resp -> HTTP resp, status->code,
  body serialised to text/sx)
- 39-test suite covering algebra, serialise/parse round-trip, mappings, bridge

Runtime notes: serialize renders string-keyed dicts as {:k v} and symbols
bare; parsed keyword tokens are a distinct type (not = to string literals) so
parse normalises; unquote-splicing is unreliable so the emitter is str-based.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 20:01:25 +00:00
parent 2ffdd6f078
commit 065fd248da
4 changed files with 328 additions and 6 deletions

View File

@@ -36,8 +36,8 @@ host — no `ocaml-on-sx` dependency.
## Status (rolling)
`bash lib/host/conformance.sh`**43/43** (4 suites: handler, middleware, router,
feed). Phase 1 DONE; Phase 2 in progress (middleware + write endpoint DONE, SXTP next).
`bash lib/host/conformance.sh`**82/82** (5 suites: handler, middleware, sxtp,
router, feed). Phases 1 & 2 DONE; Phase 3 (strangler ledger) next.
## Ground rules
@@ -91,8 +91,15 @@ lib/host/sxtp.sx subsystem APIs (feed/search/commerce/…
INJECTED resource extractor), `host/pipeline` (first = outermost). Reuses
Dream's `dream-bearer-token` + `dream-catch-with`; calls lib/acl public API.
Mute/prefs layer deferred (no blocker, add when a domain needs it).
- [ ] `sxtp.sx` — host↔subsystem wire format (align with existing spec at
`applications/sxtp/spec.sx`)
- [x] `sxtp.sx` — host↔subsystem wire format (per `applications/sxtp/spec.sx`).
Message algebra (`sxtp/request`/`response`/`condition`/`event` + status
helpers `sxtp/ok`/`created`/`not-found`/`forbidden`/`invalid`/`fail`) as
string-keyed dicts; verb/status/type as symbols (ride the wire bare). Codec:
`sxtp/serialize` (dict → `text/sx` list form, deterministic field order,
nested messages in their own list form, no `:msg` leak) and `sxtp/parse`
(`text/sx` → dict, deep keyword-token→string normaliser). Dream bridge:
`sxtp/from-dream` (HTTP req → SXTP req, method→verb, query→params) and
`sxtp/to-dream` (SXTP resp → HTTP resp, status→code, body→`text/sx`).
- [x] migrate a write endpoint (auth + permission + action): `POST /feed`
(`host/feed-write-routes resolve`) — auth ∘ ACL("post","feed") ∘ wrap-errors
over `host/feed-create`, which parses the JSON body and `feed/post`s it (201);
@@ -133,8 +140,19 @@ lib/host/sxtp.sx subsystem APIs (feed/search/commerce/…
against lib/acl's public `acl/permit?` (string atoms work — no symbol coercion
needed). The write path proves the auth ∘ permission ∘ action stack end-to-end:
401 unauth, 403 unpermitted, 201 + readback on success, 400 on bad body.
- **Remaining for Phase 2: `sxtp.sx`** — the host↔subsystem wire format. Align
with the existing spec at `applications/sxtp/spec.sx`. This is the next tick.
- **Phase 2 COMPLETE (82/82).** `lib/host/sxtp.sx` adds the SXTP codec + Dream
bridge (39-test suite). Key representation calls, learned by probing the runtime:
keywords are strings at eval time but the `serialize` primitive renders
string-keyed dicts back as `{:k v}` and symbols bare — so messages are
string-keyed dicts with verb/status/type as symbols, and a small str-based
emitter produces wire-faithful list form. `parse` needs a deep normaliser
because parsed keyword tokens are a distinct type (not `=` to string literals).
`unquote-splicing` is unreliable here, so the serializer is str-based, not
quasiquote-based.
- **Next: Phase 3 — strangler migration ledger.** Enumerate the Quart endpoints
(use the `rose-ash-services` `svc_routes` MCP tool), track migrated vs proxied,
and stand up a golden-response harness against the live Quart responses. Then
cut over the smallest whole domain (`likes` or `relations`) as proof.
## Blockers