LiteShip roadmap
Verified against the 0.9 completion campaign (2026-07-13,
v0.10.0tree). SeeSTATUS.mdfor live counts, gate totals, coverage numbers, and current telemetry watch items.
Current Phase
The pre-1.0 hardening arc closed through 0.6.0 (gates, fail-closed security defaults,
CI/release truth, the front-door cut). 0.7.0 and 0.8.0 were dogfood-driven keystone
waves: the client→server mutation channel, the form/mutation-binding primitive,
morph-opaque subtrees, Standard Schema interop, and the Cloudflare dev-path fix —
the five dashboard-blocking upstreams, all landed. 0.8.1 (2026-07-08) shipped test/bench/CI
hardening: parallel CI lane green, check:gates wired, honest benches, catalog-driven
coverage. Epic 9 (authored motion + stream spine — interpretTransition,
MotionCompiler, StateCell, wire-contract registry, active-surface gate) landed as
0.9.0 (PR #135). The 0.9 completion campaign (2026-07-13) then drove those seams
from structural to semantic completion: the motion runtime FLOOR now scrubs (#126), the
explicit TransitionProgram seq/par/choice algebra replaced the collapsed routing label
(#141), and one authored program renders identically across six targets, proven by a
differential oracle (#130) — plus graph-native stream recovery end to end (typed attested
DiscreteStateTransition), Save-Data-honouring responsive media across Astro + Cloudflare
(#140), and answer-first package metadata with a prepublish gate (#146). #145 (MCP dispatch
timeouts) is tidied; #129 (workers-off COEP escape) stays PARKED — reopen only when a real
workers-off consumer needs first-party isolation removal. #142 sharded-docs breadcrumb depth
is fixed but the sharded builder stays experimental (monolith canonical, ADR-0038).
The current phase is 0.9.0 keystone build + road-to-1.0 stabilization: close the 31 open tracker issues to production quality (QUERY read-leg, motion primitives, security/correctness, tooling), prove keystones downstream, then freeze what has earned it.
Active priorities:
- Phase B gap closure — every open issue (#104–#136) to code + test/bench + gate-green; tracker reconciled with repo truth (no false-done, no phantom claims).
- 0.9.0 keystone — HTTP QUERY read-leg (#119) + DPU adopt-under (#120) per §7 below.
- Downstream dogfood on 0.8.x — keystones are finished only when the consuming app stops producing structural product feedback, not when packages publish.
- Keep runtime correctness and hotspot coverage moving (standing watch on
reports/runtime-seams.*). - Keep security defaults fail-closed for HTML, URL, selector, style, and boundary-state surfaces.
- CI truth: parallel lane proven green; serial
truth-linuxcutover pending after Phase B. - Stability ledger toward 1.0:
_spineas the freeze ledger; freeze less than tempted.
Already Promoted
These are no longer roadmap aspirations; they are current repo reality:
- versioned authored defs with
_version: 1 - quantizer runtime branding via
_tag: 'Quantizer' - dedicated
test:redteamregression lane - same-origin-by-default runtime URL policy with explicit allowlist support
- artifact-id path-segment validation
- text-safe default LLM rendering
- shared HTML trust pipeline for stream and LLM rendering
- morph sanitization for dangerous HTML classes
- boundary-state lockdown to
--czap-*,aria-*, androle - package tarball smoke for publishable packages
- Linux truth CI plus Windows truth-preserving and browser-matrix lanes
- production middleware parity for worker-isolation headers
- live-runtime DocumentGraph (0.4.0):
loadGraphRuntime+castGraphDelta, the scene→live bridge, the AI-apply seam, theclient:svglast-mile, and headlessdualExportNode— the cast/signal substrate is now plumbed onto the live runtime, not test-only - the plumb-completeness gate (0.4.0): a package-plumb ledger + unwired-capsule floor as gauntlet phase
plumb:gate, so a built-not-plumbed primitive fails CI instead of shipping green - the front-door cut (0.6.0): the Layer-1 README with the “I want to…” router and the ≤3-package front-door budget gate, the examples ladder index, the
05-ai-patch-refusedkeystone example, the four-layer DOCS preamble, and theliteshipumbrella package - the doc-05 keystone set (0.7.0–0.8.0): the client→server mutation channel + client/form-binding primitives, morph-opaque subtrees, Standard Schema interop on the node schema, and the Cloudflare adapter dev-path fix — all five dashboard-blocking upstreams shipped
- 0.8.1 test/bench/CI hardening (2026-07-08): parallel CI lane,
check:gatesin gauntlet, honest benches, catalog-driven coverage, WGSL honesty tests (#106/#107), active-surface gate blocking (#132), wire-contract single-source (#134) - Epic 9 motion/stream spine on main (2026-07-07, PR #135):
interpretTransition,MotionCompiler,StateCell, stream recovery, generated wire-contract registry — landed as commits, not a separate semver narrative
Near-Term Hardening Epics
1. Runtime branch-hotspot sweep — watch state
The 2026-04-08 hotspot cluster and the 2026-06-08 top-10 table are both
cleared (see Completed below). The epic stays open only as a standing watch:
each gauntlet regenerates reports/runtime-seams.*, and any file that
surfaces there gets real behavior-branch tests, not synthetic padding.
2. Advisory audit cleanup — warning floor is zero
The pinned warning floor hit zero on 2026-06-10 (see Completed below);
any new advisory warning is a regression against AUDIT_WARNING_FLOOR = [].
Remaining advisory pressure is info-level strike-board pockets:
- shadow-test helpers (
tests/helpers/mock-*.ts,tests/support/) with no production imports - assertion-free
tests/generated/*files crates/czap-computetraceability evidence
Success condition:
- info-level pressure trends down without hiding real diagnostics
3. CI and release truth parity
Keep enforcing the same source of truth everywhere:
gauntlet:fullremains the canonical sequential lane- CI jobs stay semantically aligned with the gauntlet
- packaged tarballs stay installable and export-map-valid
- docs continue to point to live telemetry instead of hardcoded ledgers
Success condition:
- local truth, CI truth, and release/package truth stop drifting
4. v0.2 release-trust pivot
Code side landed 2026-06-10: the release workflow IS the OIDC pivot
(renamed Release (OIDC trusted publishing); token steps deleted;
czap ship --provenance per package), ship emits ShipSkippedReceipt
for already-published versions so workflow re-runs need no shell
fallback, and the deprecated attachViewportObserver alias is removed
(superseded by attachSignalObserver in 0.1.5).
Closed (verified during the 0.8.0 cut): the npm-side trusted publishers are
configured and proven — the 0.6.0 and 0.7.0 releases (2026-07-03) both published
every package via OIDC with provenance and zero tokens, and the dead NPM_TOKEN
repo secret was deleted on 2026-07-04. Nothing remains in this epic.
5. Client→server channel (0.7.0) — deferred nits
The channel shipped hardened against its untrusted HTTP boundary: a CSRF content-type gate, off-contract nested edge-field rejection, canonical-CapSet grants, and a full applied-graph adopt guard (decode → reseal → id/digest → topology → uniqueness — the normalized form the server emits). These smaller items were consciously NOT done in the 0.7.0 cut and are tracked here rather than lost. Both are resolved in 0.8.0:
- Resolved in 0.8.0 — Example
06-mutation-roundtripstore contract — show the compare-and-swapexpectedargument inline in the store snippet (review nit). Pure doc-comment polish; no behavior change. - Resolved in 0.8.0 —
spine-conformanceguard is non-comprehensive — it pins only an explicit SUBSET of@czap/_spinetypes, which is why theCapSet.levelsSet→array drift slipped past it (a review bot caught it, the gate did not). Make it cover every exported spine type, or auto-derive the bidirectional contract, so no future spine drift can hide behind a green gate.
Success condition:
- the doc nit is folded into the example, and the spine guard can no longer carry a blind spot.
6. 0.8.0 downstream dogfood — intake (2026-07-04)
Both dogfood consumers are live on 0.8.0 the day of the cut (clean bumps from 0.5.0, consumer audits at 0 errors / 0 warnings, prod verified headless). One validation and three parked items, recorded here so they are not lost:
- Validation — the loud-not-silent bet paid off downstream: the 0.8.0
compile-without-content-version diagnostic fired on the dashboard consumer’s
build and caught a real latent staleness hole in ITS config — its KV boundary
cache was keyed by
boundaryIdalone, not the full compiled output, so a deploy changing container decls / clamp tracks / easings without touching the boundary would have served day-stale CSS silently. The consumer now hashes the full compiled output into a per-deploy prefix. The framework’s own diagnostic found the bug; no engine change needed. - Upstream candidate — silently unfed shader uniform: the WGSL runtime
auto-feeds only the uniforms named literally
u_time/u_resolution(packages/astro/src/runtime/wgpu.ts:465-466); a hand-authored shader declaring a baretimefield compiles and renders with a silently frozen clock, because nothing feeds it. That is exactly the silent-degradation class this repo hunts: emit a loud diagnostic when a shader declares uniform struct fields the runtime will not feed. - Upstream candidate —
@propertyinitial-value override:CSSCompiler.generatePropertyRegistrations(states)(packages/compiler/src/css.ts:352) always mintsinitial-valuefrom the inferred syntax’s zero (0px/transparent/0deg), with no per-property override — so a consumer whose true resting value is not the zero (a brand color, a non-zero radius) regex-patches the emitted CSS text, fragile glue over our output format. Fix shape: an additiveoptions?: { initialValues }second parameter that validates each override parses under the inferred syntax (loud on mismatch), threaded through the second call site in the vite boundary manifest (packages/vite/src/boundary-manifest.ts:495). - Upstream candidate —
htmlAttributesMapdropped one hop from home:@czap/edgebuilds the spreadable map expressly for the Astro spread — its own docstring says<html {...htmlAttributesMap}>(packages/edge/src/host-adapter.ts:213) — butczapMiddlewarecopies six fields off the edge resolution intolocals.czap.edgeand drops exactly that one (packages/astro/src/middleware.ts:140-147); only the pre-joinedhtmlAttributesstring survives, which cannot be spread. SSR shells rebuild the map by hand fromtierswith hand-prefixeddata-czap-, reintroducing the silent-axis-miss the map exists to prevent. One-field plumb:CzapLocals['edge']+ the middleware copy + api-surface snapshot. - Rest of the consumer’s upstream backlog, re-baselined against source: a
computeShaderIntegrityproducer is a real thin gap (@czap/webships parse / verify / classify / decide but no source→sha256-…producer; the digest kernel already lives insideverifyShaderIntegrity); a Boundary→DocumentGraph node helper is real and corroborated in-repo — our owntests/helpers/graph-fixtures.tscarries the sameas unknown as SignalNodecast it self-describes as deliberately fragile glue — though (verified) the fixture does call the publicsealNode/sealGraph; the cast bridges a raw literal lacking the computedid/digestinto a sealable node. So the real gap is a from-parts node builder that computesid/digestfor you, not “no constructors” —sealNodeseals a pre-formed node, it doesn’t build one from fields; a tier-aware reveal/stagger primitive and a Save-Data/DPR responsive-image primitive are net-new surface whose ingredients exist (Client Hints already parseSave-Data; tiers ridelocals.czap) but which sit on the primitive-vs-UI-kit thesis line — owner design fork, not intake nods. - Correction to the 0.6.0 findings batch (finding #6, COEP): the original
“COEP is not overridable” framing is inverted (memory flagged it, source
confirms). COEP is consumer-overridable — it lives in
CONSUMER_OVERRIDABLE_HEADERS(packages/astro/src/headers.ts:48-51,111), set-only-when-absent, with anoptions.coepselector. What is not possible is disabling COEP while workers are enabled — cross-origin isolation is required for SharedArrayBuffer, so that floor is intentional. The only real gap is a first-party escape for the workers-off isolation case; neither dogfood site is bitten (both wantcrossOriginIsolated). Parked, not forgotten. - Owner eyeball task: the WGSL render path needs a real WebGPU device
(headless has no
navigator.gpu): openhttps://heyoub.dev/?cast=wgsl&gl=forceand confirm the orbs animate and stay round. The GLSL default path is already verified live.
Second batch (2026-07-05, production site live on 0.8.0 — scroll-choreography work; every claim below re-verified against source before recording):
- Validation — the satellite/SSR resolution chain works as designed: the
site fixed a mobile hero-fade bug by keying its tuck animation off the
boundary’s
data-czap-stateattribute and switching SSR to the request-awareresolveInitialState(UA + Client Hints + edge tier) — regime detection and server-side state resolution doing their designed jobs in production. Also a live confirmation of the parked height-axis trap: height-axis viewport boundaries impose:rootsize containment (packages/vite/src/css-quantize.ts:515-525) that clamps page wrappers, so the site’s card-deck pinning had to route aroundviewport.heightentirely. - Upstream candidate —
CzapLocals.tierserases the tier unions:packages/astro/src/middleware.ts:39types tiers asReadonly<Record<CapAxis, string>>, discardingCapTier(packages/core/src/caps.ts:13),MotionTier(re-exported from@czap/core), andDesignTier(packages/detect/src/tiers.ts:71) — consumers switch on tier values with zero exhaustiveness or typo protection. ARecordcannot express per-axis value types; the fix is the keyed struct{ tier: CapTier; motion: MotionTier; design: DesignTier }. - Upstream candidate + docs bug — raw
Requestsilently degrades to a synthetic 960px:ASTRO-RUNTIME-MODEL.md’s quickstart passescontext.requesttoresolveInitialState;ServerIslandContextis all-optional (packages/astro/src/quantize.ts:22-29) so aRequesttype-checks structurally, every field readsundefined, and resolution falls through tosyntheticValueFromCapTier('reactive')= 960 (quantize.ts:150-156) — silently. Snippet fixed 2026-07-06 — theASTRO-RUNTIME-MODEL.mdquickstart now builds the context from request headers (Object.fromEntries(context.request.headers)) and names the trap. Remaining build task: make the wrong way loud or unrepresentable in the API itself (detect a Request-shaped argument and warn, or require at least one context field) so the footgun can’t recur. - Upstream candidate —
@quantizestate bodies cannot nest@supports/@media: the state-body parser has no at-rule handling (packages/vite/src/css-quantize.ts); the consumer correctly used the documented attr-selector escape hatch. Fork to decide: support nesting, or refuse loudly at compile time — silent mis-emission is the only wrong option. - Upstream design input — no first-party CSS scroll-driven-animations
story: zero
scroll-timeline/view-timeline/animation-timelinesurface anywhere in packages, while the repo’s own topics advertise scroll-driven-animations andscroll.progressis a first-class signal. The site now pairs nativeview()timelines with boundary regimes by hand — that pairing (timeline rides the compiled state machine, reduced-motion guarded) is the shape a first-party primitive should take.
Third batch (2026-07-05, same production site — agent-tooling + doctor gaps; premises re-verified, one corrected):
- Docs-MCP — highest value, pattern proven downstream: the consumer lost
time assuming
@czap/mcp-serverwas a docs server (it exposes the czap COMMAND catalog — its README says so in line one, but the package name invites the misread; one “what this is not” line is cheap insurance), then hand-built a stateless Streamable-HTTP docs MCP (list/search/get_doc +docs://resources) over an R2-hosted bundle. Premise correction from source: NO first-party docs-bundle generator exists (docs:genfills README registry blocks only; the command catalog has no docs verb) — so the upstream shape is two parts, not one: a docs-bundle emitter plus a bundle→/mcpendpoint helper, making every czap docs site agent-callable for free. The consumer’s working reference impl is the design input. - Boundary-shadowing diagnostic — the consumer’s crown bug, still
unfiled (tracker checked: only #104/#105 exist): a hand-authored
@media/@containerrule at equal specificity, loading after the boundary’s inline<style>, silently overrode it — layout flipped at the wrong breakpoint while JS reported the right state. No specificity or shadowing lint exists anywhere in vite/audit. Candidate: a dev-time diagnostic — “non-boundary rule shadows boundary<id>output at equal specificity” — closing a whole class of silent failure. - Workers module-scope Date probe: module-scope
new Date()/Date.now()in Workers reads frozen/epoch time (the 1970 bug, hit live on a czap site). The engine-side clock law exists (doctor’s own timestamp is wallClock-injected,packages/cli/src/commands/doctor/doctor.ts:67) but no consumer-facing doctor/audit probe flags top-level ambient time reads in Workers-targeted code. czap doctor --deployed <url>— doctor names this gap itself:packages/cli/src/commands/doctor/probes-cloudflare.ts:295-298emits the ‘CSP / isolation’ probe asadvisory — doctor cannot read deployed response headerswith only a static hint (worker-src/connect-src + COOP/COEP forclient:worker). A--deployedmode that fetches the live site and verifies CSP/COOP/COEP — and the Accept-CH/Critical-CH pair while it’s there — closes the self-admitted gap and operationalizes the verify-production-not-dev lesson.
Honest scope note: neither consumer exercises the mutation channel or
bindGraphForm yet — the dashboard’s graph-write plane is its next build. The
doc-05 loop closes when the dashboard ships ON the new primitives, not merely
alongside them.
7. Platform-primitive intake — HTTP QUERY + Declarative Partial Updates (2026-07-04)
Two new platform primitives swept against every package (ten recon shards, all
claims source-anchored or empirically tested). They compose rather than
compete: QUERY is the request side (a safe, idempotent HTTP method with a
body — RFC 10008, standards track), DPU is the response side (out-of-order
HTML streaming into <?marker?> slots + sanitize-by-default insertion APIs —
Chrome 148 behind a flag, WICG, cross-browser ~2027). The verifiable-patch
envelope below is transport-agnostic and rides both.
HTTP QUERY — adoptable now; candidate 0.9.0 keystone (the read leg).
Empirically verified end-to-end: Astro 7.0.3 dispatches export const QUERY
(generic mod[method], no verb allowlist), Node/undici round-trips it,
workerd 1.20260603.1 passes it with body intact. Method string must be
uppercase 'QUERY' (lowercase dies at the parser); Cloudflare front-line
proxy passthrough is workerd-verified but needs one deployed-worker test.
- The read-leg bundle:
handleGraphQueryin core +graphQueryRoutefactory in astro mirroring the mutation channel’s 415/400/422 discipline (ADR-0030 posture: host mounts). The factory injects onlyPick<GraphStore, 'loadGraph'>— write-freedom proven at the type level. Conditional reads:If-None-Matchon the graph digest → 304, sorefreshBasebecomes near-free polling (today the read side is a hand-rolled unconditional full-graph GET,examples/06-mutation-roundtrip). Plus a retrying read sender (spec-idempotent; the POST submit correctly never retries) and a loud QUERY→POST-with-X-Czap-Queryfallback ladder. - Validator law (all four shards independently): the wire validator and
any body-derived cache key MUST be the sha256
integrity_digest/ canonical-body digest — never 32-bit fnv1a (silent-stale-304 and cache-poisoning vector when the input is attacker-supplied). Corollary: the digest excludesmeta, so host projections must never surface meta fields or the 304 lies. - The write-free gate: NOT a taint extension (a constant write has zero
tainted dataflow yet still lies to every cache) — a new region-rooted
sink-reachability fact class (handler root →
saveGraph/kv.put/writeFileSync…), hard on syntactic call paths, advisory past checker bounds, honestly named write-sink-unreachability. ADR-0034 is next free. - Hard nos: an LLM completion endpoint must never be QUERY (transparent
intermediary replay = silent double-generation); never QUERY-ify
content-addressed immutable assets (strict downgrade); no CDN body-keyed
caching exists in 2026 (Cache API
putthrows on non-GET — tested); SSE stays GET (EventSource); QUERY does not fix the navigation Vary gap.
DPU — watch-and-prepare; adopt-under, never a morph replacement. DPU replaces, morph reconciles — different operations; identity preservation, opaque islands, and the recovery protocol all survive. Chrome-148-flag-only means every rung must feature-detect with today’s path as the permanent floor.
- The differentiator: stamped verifiable HTML patches — marker names from
the stable
logicalKey(never node ContentAddresses, which self-invalidate), fragments stamped with base/result graph ids + sha256 digest =staleBaselifted to the DOM layer. Nothing in the Turbo/htmx/LiveView class has it. - Adopt-under candidates: native Sanitizer as a wrapped backend beneath
html-trust (intersection of both, differential drift guard — never
subsume); a
data-czap-sinkstream-to-element primitive composing with morph-opaque (fallback path is the tested default, useful in every browser today);streamHTML()as an LLM htmlPolicy rung (also cures the O(n²) full-string re-parse per frame); split-resolve (sync shell + per-boundary promises — only pays on KV-miss/compile; precompiled is already zero-latency); zero-JS progressive reveal for stage static exports (requires the template-mover shim or it is a content-loss bug). - Threats to pin in any adoption ADR: parser-moved
<template for>content bypasses html-trust entirely; no client hint advertises DPU support (fail-closed dual-emit only); the post-navigation runtime keys solely onastro:after-swap, which platform same-document navigation would not fire; late@propertyregistration flipsvar()fallbacks forever (any late CSS must land as one atomic style+content patch). - Declined with evidence: per-tier CSS patch delivery (≤185 B best case, 0 B typical — the stylesheet is tier-invariant by design); Critical-CH replacement (hint acquisition mid-stream is structurally impossible); HTML-patch responses replacing the JSON graph (breaks the CAS base chain).
Free defects both sweeps surfaced (actionable now, primitive-independent):
html-trust.ts scheme check misses embedded [\t\n\r] obfuscation of
javascript: (the URL parser strips them — our own runtime-url.ts documents
the class); insertAdjacentHTML/document.write are in no taint registry
(SECURITY.md’s “no third unguarded path” claim is machine-unproven) and the
*Unsafe DPU family belongs beside them; KV write-back on boundary-cache miss
is awaited on the request path (waitUntil it); CDN caching emits no Vary
on any client hint while HTML is tier-varying (latent wrong-tier serving);
ResumptionConfig.timeout is write-only dead config (no AbortSignal, no
retry, on already-idempotent recovery GETs); czap:request-snapshot is
dispatched with no shipping-runtime listener (only a component test subscribes —
dead dispatch); no audit surface ever reads
consumer app code (the *Unsafe sinks are exactly what consumers will copy).
8. Cross-reference + staleness intake (2026-07-06)
A four-agent cross-reference of this roadmap and persistent memory against an
external LiteShip audit, plus a whole-doc staleness sweep (every root doc read
cover-to-cover, not grepped). Every claim below was verified against current
source. Corrections were folded into the Epic-6/7 bullets in place — COEP finding
#6 (inverted framing fixed), the CzapLocals.tiers union anchors, the
node-builder framing (the fixture does use sealNode; the gap is a from-parts
builder), the czap:request-snapshot listener wording, and the resolved
resolveInitialState doc snippet. New items are recorded here.
Guiding rule for this whole section (per owner directive): where a doc “overclaims,” the cure is to build the behavior up to the claim — usually a few LOC of plumbing/types — not to nerf the claim. Nerf only what is wrong in intent.
Net-new engine candidates (external audit, source-checked):
- SSR resolution receipt. Generalize the raw-
Requestfix into a fallback-reason artifact:resolveInitialState(and the SSR path) should be able to report which source drove initial state — explicit context / UA / Client Hints / edge tier / synthetic fallback — so the whole silent-fallback class goes loud, not just the 960px case. Anchor:resolveInitialState+syntheticValueFromCapTier(packages/astro/src/quantize.ts). Regression: a test asserting the receipt namessyntheticwhen no signal is present. - Docs-bundle content-hash manifest + staleness gate. Extends the filed
Docs-MCP item (batch 3). The emitter should stamp a versioned,
content-addressed manifest (package version + source-file list + sha256) and
run a stale-doc detection pass before emit, so agents consume sealed,
provenance-carrying docs instead of scraping. Premise (verified): no
docs-bundle generator exists —
scripts/gen-docs.tsfills README registry blocks only. Shape:docs:bundleemitter →docsMcpRoute(bundle). - Consumer-audit doctor mode. Concretize the “no audit surface reads
consumer app code” free defect: a scoped, read-only
czap doctor/audit --consumer-appmode that scans consumer source for known LiteShip integration smells — unsafe HTML sinks, raw-RequestresolveInitialState, missingVary, boundary shadowing, module-scope WorkerDate, hand-builtdata-czap-*, direct graph mutation. Seed: the taint registry (packages/cli/src/lib/taint-policy.ts); today the corpus is framework packages only (packages/gauntlet/src/node-context.ts), never arbitrary consumer app code.
Staleness-derived build items (build to the claim; do not nerf the doc):
- Route the dev inspector through the trust pipeline. SECURITY.md asserts
“no third unguarded
innerHTMLpath.” The dev-only boundary inspector (packages/astro/src/runtime/inspector/panel.ts— four rawel.innerHTML = …writes, one interpolating adata-czap-stateattribute; registeredastro devonly,integration.ts) is a third path. Route those writes throughcreateHtmlFragment/assignInnerHTMLso the claim is literally true everywhere. Small (4 sites), dev-only, low-risk — the point is the claim stays ambitious and the code rises to it. (SECURITY.md was scoped to “in the shipped runtime” 2026-07-06 as honest interim; this item removes the qualifier.) - Canonicalize the URL scheme check (already Free Defect #1, restated as the
build target). Strip
[\t\n\r]from the normalized value before thestartsWith('javascript:')test inisDangerousAttribute(packages/web/src/security/html-trust.ts) — the WHATWG URL parser strips them, sojava\tscript:currently survives and later executes (documented inruntime-url.ts). Red-team regression intests/regression/red-team-runtime.test.tscovering the embedded-whitespace variants. Makes SECURITY.md’s scheme-strip claim true. - Add
insertAdjacentHTML/document.writeto the taint registry as defense-in-depth —packages/cli/src/lib/taint-policy.tslists onlyinnerHTML/outerHTMLas assignment sinks. No live sink uses them today, so this is a gate that catches a future one (and the DPU*Unsafefamily when it lands). Makes SECURITY.md’s “no third unguarded path” machine-checkable. - Docs-plumbing debt (a subsystem isn’t done until it’s in the prose chain). The 0.8.0 seams are documented in README/GETTING-STARTED/GLOSSARY/ PACKAGE-SURFACES/DOCS, but ARCHITECTURE.md and ASTRO-RUNTIME-MODEL.md carry no mutation-channel section, and ADR-0025 (Workers static-assets boundary-CSS), ADR-0026 (Receipt-DAG compaction), ADR-0027 (Cell value→wire) have no prose home in the three architecture docs. STATUS.md got a 0.7→0.8 feature refresh 2026-07-06; its test-count/timing snapshots still carry the 2026-06-25 baseline (re-capture from a fresh gauntlet). Confirm exact placement before writing — docs are sacred.
Forgotten-from-memory (cross-ref against memory, source-traced):
- WGSL integer/unsigned vector uniforms mis-laid-out and mis-written
(CONFIRMED 2026-07-06, source-traced). Scalars are fine end-to-end — the
compiler infers
i32/u32(packages/compiler/src/wgsl.ts:120-122) and the runtime writes them viasetUint32(packages/astro/src/runtime/wgpu.ts:292). The gap is integer/unsigned vectors:wgslTypeInfo(wgpu.ts:169-185) has cases only forvecNf, sovec2i/vec3i/vec4iand thevec2<u32>/vec2<i32>spellings fall todefault→{ align: 4, size: 4, kind: 'float' }. Three failures at once: (1) layout — avec2iis 8 B / 8-align, avec4i16 B, so 4/4 under-sizes the field and miscomputes every later field’s offset; (2) write —kind: 'float'routes tosetFloat32(offset, value as number)(wgpu.ts:294) but the value is an array, so[x,y] as number→ NaN; (3) silent — thedefaultswallows the unknown type with no warning. Not reachable via inference (the compiler only emitsvecNf,wgsl.ts:114-117); reachable only through a hand-authored@wgslstruct field — the same surface as the unfed-uniform finding. Fix, loud-first: (a)warnOncein thedefaultbranch on any unrecognized uniform type (converts the whole class to loud even before support lands); (b) addwgslTypeInfocases for the integer-vector types with correct align/size + integer kinds, and per-componentsetInt32/setUint32write branches (wgpu.ts:296-301). Compiler side needs no change. Aside:boolis in theWGSLTypeunion but isn’t host-shareable in a uniform buffer; it also falls todefault— reject it explicitly. audit --findingsstdout/stderr contract. The flag puts findings on stdout- receipt on stderr, diverging from the CLI’s “one JSON receipt on stdout” contract. A 1.0 API-stability decision to settle, not a bug.
audit --consumer --skip-structureopt-out. Shipped as surgical-suppress; make the choice stated, not a silent default (upstream-findings-post-040#4).
Invariant-drift watch (not a defect yet):
- Boundary-CSS self-containment. LAW:
CompiledOutputs.cssis the full ordered stylesheet.serializeBoundaryCss(packages/astro/src/fetch-layer.ts:87-94) now conditionally prependspropertyRegistrations/containerQueriesguarded by a!outputs.css.includes(...)substring check. No doubling today, but a whitespace-normalization false-negative on that check re-introduces the double-emit. Reassert “emit onlycss,” or replace the substring check with a structural one.
External-dep note. The QUERY mod[method] dispatch claim (Astro dispatches
export const QUERY with no verb allowlist) is not re-verifiable in-repo (no
astro in the tree, only @czap/astro); it stays recorded as empirically
verified end-to-end. The read-leg factory it implies
(graphQueryRoute/handleGraphQuery) is confirmed absent — correct, it is a
proposal — and would mirror the existing graphMutationRoute
(packages/astro/src/graph-mutation-route.ts; 415/400/422 discipline).
9. Authored Motion + Self-Managing State over DocumentGraph (2026-07-06)
The keystone direction, from the convergence of a four-agent repo deep-read and two
external passes — all three landed on the same finding independently. Full
impl-ready spec lives in docs/internal/design-authored-motion-state.md
(committed working note — docs/internal/ is normally local, force-committed here
because a canonical doc references it; ratified as ADR-0035 —
docs/adr/0035-motion-is-intent-not-target.md).
Tracked as epic #130; taxonomy decision #131; children #124
(reveal/stagger), #126 (scroll-timeline); sibling #125 (responsive-media).
Thesis correction (kill “not a UI framework”). LiteShip is a multimedia-native adaptive UI compiler/runtime — not a component library. It competes with Webflow/Flash by owning the authoring model, projection graph, runtime, media surfaces, AI-safe patch seam, and compiler — not a button zoo. The “projection truth engine” framing was academic smoke; the honest boundary is “no component zoo,” not “not a UI framework.” (Tagline threaded through README / ARCHITECTURE / SKILL 2026-07-06; ratified as ADR-0035.)
The keystone (named in source): TransitionNode is typed, content-addressed,
and in the graph — and nothing reads it. graph-lower.ts lowers PoseNodes to
discrete per-state channels but never consults TransitionNode.routing/durationMs.
LiteShip is not missing an animation library; it is missing the interpreter that
turns the motion data it already models into motion. The substrate is present but
orphaned: PoseNode (keyframe), TransitionNode (tween) + EdgeType
seq/par/choice (sequencing algebra), PolicyNode (reduced-motion/tier/budget gate),
Easing.springToLinearCSS (spring→CSS linear(), works in miniature),
scene-bridge.writeContinuous (live per-frame eased→DOM CSS-var writer, ships but
one scalar), @starting-style/@property emission. Law already type-encoded: a
Pose is content-addressed, per-frame transients are not → continuous writes never
patch the graph per frame. The one real GSAP-core gap: interpolate is numeric-only
(no color/unit/transform).
Step-0 decision (made): motion is an authored INTENT, not a projection target.
ProjectionNode.target (css/glsl/wgsl/aria/ai/config/svg), ExportNode.carrier
(astro-page/video/svg/ship-capsule/receipt), LadderTarget
(css/glsl/wgsl/aria/ai), and RuntimePhase all lack motion — deliberately. A
MotionIntent lowers into a css projection plan + a runtime leaf-write plan
(+ optional gpu/adapter). No change to any target/ladder/phase union.
The vertical slice (build this first, nothing else): (1) typed value model
interpolateTyped (fix numeric-only), (2) TransitionNode interpreter
interpretTransition (the keystone), (3) N-property writeContinuousMap
(generalize the one-scalar writer), (4) native-CSS MotionCompiler arm (reuse
springToLinearCSS + @starting-style), (5) StateCell/ProjectionState — a typed
authority over the EXISTING coarse graph/boundary/quantizer/dirty model, NOT a new
fine-grained reactive runtime (do not build SolidJS), (6) one reveal end to end as
proof. Done = that reveal compiles from graph and runs, gauntlet-gated. Then expand.
Guardrails (Laws): continuous-write-never-patches; sugar is data over canonical
intent, no behavior authority (precedent: @czap/scene already ships
fade/pulse/ease/syncTo sugar over primitives); native CSS first + typed
runtime floor forever; GSAP barred as a first-party dep (its license restricts
Webflow-competitive no-code animation builders — exactly this product), Motion
(motion.dev, MIT, vanilla, no React) is the eventual optional adapter, AOS
absorbed not depended-on (its data-attr+IO+CSS pattern is already LiteShip’s, done
better); WGSL honesty (#106/#107 — unfed-uniform diagnostic + integer-vector
mis-layout) lands before motion sugar, because a multimedia-native framework
cannot ship silent shader lies.
Named “free batteries” (downstream, not yet built — recorded so they are not
lost): DocumentGraph is already the creative-document/design-file format
(content-addressed, GraphPatch = undo/redo/collab/AI-edit substrate); Component
is a Webflow-symbol-shaped primitive (binds boundary/styles/slots), not a React
component; @czap/stage is the export dock (one graph → page + video, proven to
share a source digest); assets beat/onset/waveform + scene beat-binding = an
audio-reactive path (flagship demo candidate: audio-reactive adaptive landing page
→ web + video from one graph); cap-ladder is a capability-aware-design product
engine, not just a guard; command/mcp-server/docs-bundle is the AI co-author
backplane; QUERY (#119) + GraphMutation are the collaborative-editing sync
skeleton; DPU (#120) is the future live-preview path. Each is a follow-on after the
motion/state spine proves — not a parallel build.
Doctrine — the rigor taxonomy (now in SKILL.md §16): Law (never break: security, graph identity, validation, no silent
drift) · Contract (public API promise) · Receipt (evidence) · Diagnostic
(loud, not always blocking) · Watch item (known risk) · Recipe (example, not
law) · Preset (data over intent, not behavior authority). The cure for “models
call everything an invariant and cage the product” — only Laws are inviolable; rigor
is a seatbelt that lets the product carry more expressive UI/media safely, not a
speed limiter.
Completeness gate (do not build a FeatureContract subsystem). The gauntlet/audit
machinery already has the authority ratchet (gates earn blocking only via
red/green/mutation fixtures, authority.ts), FactGate, the evidence recorder,
ambition-proof (the advisory high-ambition/low-proof heat map), and symbol-level
orphan detection (audit OrphanCoverage). The missing oracle is field-level:
an active modeled surface must have readers for its load-bearing fields, else a
blocking Finding — a TransitionNode with routing/durationMs that no interpreter
reads is dead data in a live type. First red fixture is the current repo state.
Mechanical certainties block; heuristics advise. Tracked as #132 (the acceptance
gate for #130 — “done” = this gate green). Obligations derive from the NodeFamily
union + a status tag, never a hand-maintained string mirror. @czap/audit produces
the field-reader facts; @czap/gauntlet decides over them (no typescript dep in
gauntlet); ast-grep is a fast smoke detector, not the authority.
New-brain re-frame of the upstream ledger (2026-07-06). A dogfood consumer
(signals-only feed) surfaced three stream gaps; re-examined from where LiteShip is
going, not where it is now. Load-bearing reframe: stop treating stream resumption
as its own recovery protocol — it is the same problem as multi-client collab-sync.
The mutation channel (graph-mutation-client.ts: currentBase/adopt/refreshBase,
GraphPatch base/result identity + chained receipts) is already a content-addressed,
receipt-carrying replay machine; the QUERY read-leg (#119) is its read side. The
discrete/continuous law is the replay discriminator: only discrete crossings are
replayable graph events; continuous transients (scroll.progress/pointer/audio/
time) are correctly ephemeral and must NOT replay. So:
- #133 (signal gap-replay): do NOT widen the SSE
{ patches }payload to carry signals — that manufactures a divergent second substrate AND blindly replays ephemeral transients. Interim: wire the deadczap:request-snapshottorefreshBase/adopt+ snapshot re-sync for missed discrete crossings. Full: signals asStateCells, patch/receipt-chain replay via #119. The consumer’s app-side “WO-8” is the workaround until this is native. - #134 (wire-contract): the
czap:*/data-czap-*contract has zero docs coverage → GENERATE it from a source registry + drift guard (ADR-0028/0018 pattern), never a hand-written page (SKILL §16). Same issue folds gap-3: fix the stale “back-compat” docstring — ADR-0028 already decided plain-elementclient:*is first-class. - Old-brain corrections applied: #122’s “…or remove it” options for
ResumptionConfig.timeoutandczap:request-snapshotwere struck (both are unfinished features, not dead code — complete-don’t-nerf); #119 reframed from a “polling perf nicety” to the collab-sync/gap-replay substrate. - Motion-epic-shaped (build per #130, not standalone): #112 (node builder, a
lowering prerequisite), #118 (SSR receipt = proto-
ProjectionState), #108 (tiers struct = typed-state-authority down-payment), #110 (@quantizenesting = decide with theMotionCompilerarm). Subsumed: #104 scroll-warning + batch-2 scroll story → #126.
Completeness ledger — what SHIPPED in the Epic-9 slice vs what’s still owed (post-merge
audit, 3-agent cross-ref against issues + design note + this roadmap). The slice landed
clean: #106/#107 (code + tests in wgsl-honesty.test.ts), #124 (reveal shipped), #130
(machinery + reveal), #132 (advisory→blocking), #133 (interim), #134 — a contracted
vertical slice, gauntlet-enforced. #125 responsive-media DPR min-width base fixed in
0.9 Tier A; Save-Data/Client-Hints → full <picture>/srcset projection tracked in a
follow-up issue. Four issues are
PARTIAL — do NOT close as fully-done (a piece shipped, the whole is owed):
- #124 stagger: reveal/
@starting-style+examples/07-stagger-revealpreset shipped; multi-step seq/par chains deferred to follow-up. - #125 responsive-media: DPR min-width + core projection shipped; advanced Client-Hints host wiring deferred (see follow-up issue).
- #126 scroll-timeline: the native-CSS
@supports (animation-timeline: view())floor + reveal’sview/scrolltrigger shipped (MotionCompiler); the standalone scroll-timeline intent primitive did not. - #118 SSR receipt:
StateCell.StateResolutionReceiptis a runtime proto only; the astro SSR-source receipt inquantize.ts(name-the-source-that-drove-initial-state) is untouched. - #122 hardening sweep: only the dead-
czap:request-snapshotorphan was cured (wired in #133);waitUntil/timeout→AbortSignal/Vary/ boundary-css remain. Next motion primitive (scoped OUT of slice 1, so not a miss): multi-stepEdgeTypesequencing —interpretTransitioncurrently treatsseq/par/choice_thenas a 2-step start→end; true chained-TransitionNodesequencing (the seq/par/choice algebra) is the natural follow-on, pairs with #126.
#136 (sharded low-memory docs build) — resolved by ADR-0038 (2026-07-08). Monolith
TypeDoc (pnpm run docs:build) is canonical; docs:build:sharded is experimental only.
Sharded breadcrumb depth is a separate follow-up issue.
Completed Since Last Revision (2026-05-17)
Epics #1 + #2 — hotspot sweep and advisory floor (closed 2026-06-10, PR #11).
The live runtime-seams top-10 (wgpu 4%, ffmpeg-probe 15%, host-browser
context 15%, scene-dev server 20%, gauntlet cmd 30%, video decoder 47%,
audit CLI adapter 52%, three 1/2-branch harness files) all prove 100%
branches with behavior tests. The 10-warning fallback-laundering floor went
to zero honestly: doctor probes surface read/parse failures as structured
details (Readout<T>), the integrity detector credits only catches whose
error binding is meaningfully read before/within the fallback return, and
the two deliberate fail-closed defaults carry allowlist reasons. Same PR:
coverage/load-aware scaledTimeout test policy, consumer-mode dist-truth
verification (export-target-missing), and a real-browser e2e of the built
Astro example.
Epic #4 — @czap/cli coverage back to package defaults (closed 2026-05-17).
The v0.1.0 ShipCapsule slice landed with sub-85% coverage on
capsules/ship-emit.ts (44%), commands/ship-verify.ts (82%), and
ship-manifest.ts (75%), and the per-package override at
scripts/merge-coverage.ts PACKAGE_THRESHOLD_OVERRIDES.cli temporarily
lowered the thresholds to 75/75/78/60. Post-v0.1.1 (v0.1.2 candidate)
closed the gap across eight targeted commits on
claude/improve-dev-experience-7f0as:
ship-emit-branches.test.tsliftedcapsules/ship-emit.tsto 100% across lines/branches/functions/statements via direct invocation of the capsule (schema validation, write-path, invariantcheckarms) rather than going through the orchestrator.ship-manifest.test.tsgained hand-crafted tar fixtures covering parseTar PAX header (typeflag'x'), GNU long-name (typeflag'L'), and USTARprefix-field path-reassembly arms — lifting line coverage from 75.63% → 95.79%, branches 51.06% → 76.59%.ship-verify-verdicts.test.tsextended with the--capsule=equals form, three exit-1 emitError arms (missing tarball, tarball-not-found, capsule-not-found), the recompute-failure → Incomplete arm, the thirdShipCapsuleDecodeErrortag (invalid_shape), and the individual mismatch arms tested in isolation — liftingship-verify.tsto 96.82% lines / 91.66% branches.- New
tests/unit/cli/commands/ship.test.ts+verify.test.tsfilled the canonical-location gap so every dispatch verb has a peer smoke test alongside doctor/glossary/help/completion/version. - New
tests/unit/cli/idempotency.test.tscovered thetryReadCacheforce-bypass and file-present arms without going through the ffmpeg-gated integration test. - glossary/version pretty-mode tests covered
prettyEntryand the stderr one-liner. - dispatch-sugar gained a verb-routing block covering scene / asset /
capsule case arms + the
verify(no-args Unknown verdict) path. - asset-analyze covered the
onset+waveformprojections + the cache-hit arm.
Final cli aggregate: 85.64% lines / 85.06% statements / 86.84% functions
/ 77.06% branches — all above the 85/85/85/75 package default. The
cli override was removed from scripts/merge-coverage.ts and the
drift guard at tests/unit/meta/coverage-config.test.ts was tightened
to assert the override no longer appears.
Earlier Completed Work (2026-04-23)
Spec 2026-04-23-capsule-factory-video-stack-design.md shipped with 5 atomic phases:
- Capsule factory kernel + 7-arm assembly catalog (ADR-0008)
- Spine runtime-gap closure (ADR-0010)
- Scene composition stack on existing ECS (ADR-0009, ADR-0002 amended)
- Asset capsules + analysis cachedProjections
- CLI + MCP dual-audience surfaces
- ADR-0007 (adapter vs peer framing) resolved
flex:verify dimensions expanded to 7 (added CapsuleFactory).
Product-Adjacent Future Epics
These are real future LiteShip / CZAP directions, but they are not promises for the current hardening wave.
Component-local data loading
Goal:
- define a host-safe, boundary-aware data-loading model that does not accidentally turn LiteShip into an RPC layer
Entry criteria:
- current runtime/security/package hardening wave is stable
- dogfooded apps show repeated loader patterns that belong in LiteShip core
Assembly mapping: cachedProjection capsules keyed on (url, params, auth-scope). Scenes and hosts reference loaders by capsule id; the factory emits decode + cache-invalidation harnesses.
Stateful edge AI bindings
Goal:
- offer explicit, host-owned AI/stream bindings at the edge without making the frontend runtime depend on a vendor-specific control plane
Entry criteria:
- current trust boundaries for stream/LLM/runtime URLs are stable
- receipt/authenticity semantics are made explicit enough to build on safely
Assembly mapping: receiptedMutation capsules at site: [‘edge’], paired with policyGate capsules for authorization.
Plugin-as-integration sidecar
Goal:
- make the Vite/Astro integration path feel like a coherent sidecar without collapsing package boundaries
Entry criteria:
- package smoke, CI truth, and support-matrix policy stay stable across dogfooding
Assembly mapping: refinement of the existing siteAdapter arm. Vite plugin + Astro integration become capsule instances with declared capabilities.
Explicit Non-Goals For This Wave
- built-in auth/session system
- ORM/storage/queue stack
- general RPC/server-action framework (the 0.8.0 mutation client and form binding still submit one validated graph-patch seam, not arbitrary remote calls)
- backend/router stack expansion
- stateful edge AI substrate implementation
- component-local data loading implementation
Stop Condition
This hardening wave is done when:
- correctness seams are closed
- adjacent debt surfaced by that work is also closed
- red-team findings are fixed or deliberately justified
- package distribution is proven
- CI truth matches local truth
- dogfooding no longer produces structural product feedback
- remaining work is genuinely micro-optimization or speculative enhancement