v0.7.0
[0.7.0] - 2026-07-03
The client→server mutation channel — the return leg of the stream. SSE pushes
server→client; this is the other direction, and it rides the AI-cast refuse-seam so a
human client's edit is validated exactly like a model's proposal.
Added
@czap/core— the client→server graph-mutation channel.handleGraphMutation(request, { loadGraph, saveGraph })is the transport-agnostic server core: decode a client-proposed
GraphPatch→validateGraphPatchProposal→ compare-and-swapapplyValidatedPatch→
persist, returningapplied(new graph),refused(invalid patch — validation or a
concurrent-write CAS miss), orerror(a server-side store failure, retryable).
sendGraphMutation(url, patch)is the client sender: it validates the response shape, then re-seals
and re-validates the applied graph — it must be the NORMALIZED form the server emits (deduped unique
nodes/edges, id and digest addressing its content, no dangling edge or cycle) — before adopting it (a
forged, miswired, or malformed base is refused, not stamped against), and maps a transport failure or
a non-JSON body toerror— one shape to consume, never a raw throw. A patch cast against a stale base is
refused (optimistic concurrency for free); only a validated patch mutates the graph, which
re-addresses. The host owns theGraphStore(the authority boundary, ADR-0015); LiteShip owns
the gate. Full rationale: ADR-0030.@czap/astro—graphMutationRoute(store). The host route adapter: a plain
(request) => Response(theczapFetchLayershape) that drops into an Astro API route
(export const POST: APIRoute = ({ request }) => graphMutationRoute(store)(request)). 200
on apply, 422 on refusal, 400 on a malformed JSON body, 415 on a non-application/jsonbody.
@czap/astroinjects no route — the endpoint, store, and authority are the host's.examples/06-mutation-roundtrip— a runnable SSR app proving the round-trip end to end
(client proposes → server validates + applies + persists → the stale re-proposal is refused).
Security
- CSRF hardening on the route.
graphMutationRouterequiresContent-Type: application/json
(415 otherwise), forcing cross-origin POSTs through a CORS preflight so a simple-request can't
smuggle a patch to a cookie-authed mount.Request.json()would otherwise parse atext/plain
body just fine. Host session/origin auth remains the host's. - Refuse-seam hardening — off-contract nested edge fields. The AI-cast validator now enforces
additionalProperties: falseon the edge object (from/to/type), not just the op envelope. Before,
a patch could smuggle an extra field onto an edge (a blob, or a__proto__key); the graph digest
addresses only[from, to, type], so the field rode into the sealed graph un-addressed — the
persisted bytes diverging from the content address. Now rejected at every schema depth, on both
the channel and the AI-apply paths. - Fail-closed reader rejects invalid edge types.
decodeDocumentGraphnow validates an edge's
typeagainst theEdgeTypeenum (seq/par/choice_then/choice_else), not just that it is a
string — closing a shape gap for ANY host lowering an untrusted graph, not only the mutation channel. - Policy-grant corruption closed at the root. A policy node's
grantsis now a validated,
canonical CapSet schema (was opaque): a malformed or non-canonical grants is rejected by
isWellFormedNodeon the channel and AI-apply paths alike, so an untrusted client cannot seal a
wire-ordered CapSet under a divergent content address. (See Breaking — the underlying type change.)
Breaking
CapSet.levelsis a canonical array, was aSet. ASetJSON.stringifys to{}(so a
policy patch over the channel silently lost its grants) and its insertion order mis-addressed the
same logical set.levelsis now a deduped, ladder-sorted array: JSON-faithful and
content-address-deterministic.Cap's combinators (has/union/intersection/…) are unchanged;
only direct.levelsaccess moves fromSet(.has/.size) to array (.includes/.length).
Internal
- Bench directive-overhead gate: window-invariant estimator. The hot-path overhead ratio is now
the median of per-sample PAIRED ratios (each pair timed adjacently), not the ratio of medians —
robust to the whole-sample load bursts on contended CI runners where the old estimator false-positived.