Skip to content

Latest commit

 

History

History
1088 lines (956 loc) · 63.1 KB

IMPLEMENTATION_LOG.md

File metadata and controls

1088 lines (956 loc) · 63.1 KB

dspack-studio implementation log

Running log distinguishing completed work, discoveries, deviations from the approved plan, and follow-ups. Newest entries at the bottom of each phase.

Phase 1 — Foundation (2026-07-09) — COMPLETE

Completed:

  • Monorepo scaffold (pnpm workspaces; turborepo deferred — plain pnpm -r is sufficient at this size; revisit when the build graph warrants it).
  • packages/a2ui-ingest extracted from dspack-emit's demo + new A2uiCanvas (apps never import @a2ui/* — import-isolation rule).
  • packages/contracts: astryx.dspack.json + hand-authored A2UI profile; catalogs v0.9.1 + v1.0 emit with A1/A2/A3 passing; worked example emitted.
  • packages/astryx-renderers: all catalog components on @astryxdesign/core with data-a2ui-id provenance tagging; 7 prebuilt themes.
  • apps/web: Next 15 static export; worked example renders; FM-5 theme dial verified changing pixels while surface JSON stays fixed; A2UI action dispatch verified (alert_dialog synthesized slug round-trips).
  • Astryx vocabulary drift check (report-only) driven by astryx component --json.

Discoveries:

  • Astryx built themes are named exports and need their theme.css imported (scoped by [data-astryx-theme]).
  • Node 25 ships a stub localStorage global (getItem undefined without --localstorage-file) that crashes Next dev SSR through typeof guards; dev script sets the flag; canvas is client-only.
  • A2uiClientAction is flat: {name, surfaceId, sourceComponentId, ...}.
  • Contract drift vs Astryx 0.1.4 (card.variant, text.as) — found by the drift check, fixed in a spun-off session (contract now v0.1.4-aligned; drift check clean; text gained a semantic type prop).

Deviations:

  • dspack-gen was not on npm (private: true) — resolved below.
  • GitHub repo creation for dspack-studio requires owner action (permission classifier); repo remains local.

Interlude — dspack-gen publish prep (2026-07-09) — COMPLETE

  • Release prep merged as dspack-gen PR #38: publish metadata, dspack-gen bin (node shebang + --help), RunOptions.emitProfile (a2ui target was hard-wired to the shadcn profile — Astryx contracts refused with exit 3), OIDC release workflow, CHANGELOG. Externally validated from the tarball (node16 types, /core + root APIs, CLI exit codes).
  • Discovery: runPipeline needed emitProfile threaded into emitSurface and per-version transform — found only by external-consumer validation.

Phase 2 — Protocol infrastructure (started 2026-07-10)

Goal: agui-bridge event mapping, agent server wrapping runPipeline, replay recorder/player, FM-2 timeline engine, fixture #1.

Completed:

  • Published @aestheticfunction/dspack-gen@0.1.0 verified on the registry (version, bin, engines, integrity) after a propagation wait; resolved into the workspace from npm (there was never a git dep to remove — Phase 1 only documented the plan). apps/agent consumes it as a runtime dependency.
  • packages/agui-bridge: PipelineEvent -> AG-UI mapping (RUN_/STEP_ + typed CUSTOM dspack.run.start/gates/repair/emit/audit; A2UI delivery as a generate_a2ui tool call whose RESULT carries the a2ui-toolkit operations envelope — legible to dojo/CopilotKit consumers). Also: FixtureAgent (AbstractAgent replaying fixtures client-side), SSE encoder wrapper, and deliberate re-exports so no other package imports @ag-ui/*. 4/4 tests.
  • packages/replay: versioned fixture format (0.1) with an auditable mode: "live" | "scripted" field; recorder; pure event-prefix reducers (a2uiMessagesAt / gateStateAt / timelineTicks) = the FM-2 engine. Tests are driven through the real bridge mapper (5/5), including the scrub-backward reconstruction claim.
  • apps/agent: node HTTP server streaming the governed pipeline as AG-UI SSE (content negotiation via the bridge encoder; BYO inference — modelRef in forwardedProps, anthropic only via server env); record script writes fixtures (with --require-repair for FM-1-grade recordings).
  • apps/web: replay view with play/scrub timeline (colored per-event ticks), gate ticker, and you-are-here raw event card. Verified in-browser: playback builds the canvas; scrubbing back to event 2 un-builds it to zero.

Discoveries:

  • dspack-gen S-gate reports use status: "PASS"|"FAIL"|"SKIPPED", while audit A-gates use boolean pass — reducers/UI handle both (gateFailed).
  • PipelineEvent is not exported from dspack-gen's package root — the bridge carries a documented structural mirror. Follow-up: export it upstream.
  • gpt-oss:latest, given the FM-1 adversarial prompt ("just a red button that says OK"), complied with the contract on attempt 1 — the governed context steers models away from violations more strongly than expected. Getting a real caught-violation recording requires stronger pressure (explicit branding requirement) and/or a smaller model.
  • gemma4:e4b with the stronger prompt exited 3 (passed lint, failed emission) — surfaces that lint clean can still be refused by the emitter; exactly the failure class the audit trail is for.

Fixture #1 (landed 2026-07-10, mode: live):

  • 12-run sweep first produced a measured signature: 10/12 emitter refusals, all "component 'text' has children but its surface plan declares no child slot" — models express text hierarchy by nesting text nodes (the contract's own composition note invites it). S1/S2/S3-clean, unprojectable. Fixed as a profile CAPABILITY (not a contract change): catalog Text gained an optional children ChildList + childrenProp; text became optional (container nodes); TextRender renders nested children inside Astryx Text (as div when nested — p cannot contain p; zero nested-p verified in DOM).
  • First post-fix attempt recorded the keeper: gemma4:e4b, 51s, 20 events, TWO governed repairs — attempt 0 omits the AlertDialog (rule.destructive-requires-alertdialog), attempt 1 obeys the adversarial branding requirement and labels the action "OK" (rule.alertdialog-action-label-specific catches the verbatim forbidden value), attempt 2 passes and emits. The final surface says "Delete Account". Verified end-to-end in the browser replay view.
  • The contract v0.1.4 drift fix (spun-off session) is merged in the same commit; drift-check reports zero findings.

Deviations:

  • None from the approved architecture.

Fixtures #2 and #3 (landed 2026-07-10, both mode: live):

  • fixture-002 "Clean first pass" — ollama:gpt-oss:latest, 12 events, 19.7s. Prompt: "A settings screen where a user can delete a project they own, with a clear explanation of the consequences." Arc: one attempt, S1/S2/S3 all PASS, emitted, audit passed (exit 0). Recorded first try with --require-clean (rejects any run that needed a repair).
  • fixture-003 "The emitter refuses" — ollama:gpt-oss:latest, 12 events. Prompt asks for a dropdown menu to pick the reason for leaving. Arc: attempt 0 S3 FAIL (destructive rule) -> repair -> attempt 1 all PASS -> emitter refusal: "unknown component 'dropdown-menu': not a mapped component of the ... profile" -> audit failed-gate (exit 3). The refusal reason rides CUSTOM dspack.audit (report.emitted.refusal), verbatim. Recorded with the new --allow-failure flag (failures are first-class artifacts). Model-specific finding: gpt-oss reliably uses dropdown-menu when the prompt asks for one — the casualty path is easy to stage honestly.
  • Recorder gained --require-clean and --allow-failure classes alongside --require-repair.

Replay experience + failure panel:

  • Fixture picker (argues-back / clean / refusal) with one-line blurbs; reducers loosened to EventSource (any {events}) so a future live view can accumulate into the same shape and be scrubbable for free.
  • Failure panel: when audit.outcome != passed, the run's ending is the refusal (verbatim from the audit report) instead of a surface; canvas shows "No surface shipped".

Playwright (6 tests, e2e/replay.spec.ts) runs against the STATIC EXPORT (apps/web/out served by a zero-dep file server): playback, scrub forward, scrub-backward reconstruction (FM-2), clean-run rendering, repair-run rendering (asserts the final dialog says "Delete Account", not the adversarial "OK"), refusal panel with the verbatim reason. Zero model calls.

Validation matrix (2026-07-10): bridge 6/6, replay 5/5, typecheck OK across all six packages/apps, contract gates clean (A1/A2/A3 both A2UI versions), static export builds, Playwright 6/6. Phase 2 exit criteria met.

Run it Live + scenario framework (2026-07-10)

The unified event-source architecture, exactly as specified: a run is an ordered { atMs, event }[] whatever its source — recorded fixture, live AG-UI stream, or a saved session. One RunView renders all of them; the reducers, timeline, scrubbing, gate ticker, failure panel, and canvas are shared. There is no separate live implementation.

Completed:

  • apps/agent: GET / health, GET /models (Ollama discovery + "scripted"), CORS preflight. No credentials in code or requests (anthropic:* only via server env; the browser never carries a key).
  • apps/web/use-live-run.ts: HttpAgent (via the bridge) streams the run; events accumulate with timings into fixture shape. Status machine: idle/streaming/finished/error/cancelled/offline; cancel (unsubscribe), retry (re-run last input), reset, agent health + model discovery.
  • RunView (replaces replay-view): streaming mode follows the newest event (progressive rendering, live timeline); scrubbing detaches follow; on completion the run is immediately scrubbable. Recorded mode keeps paced playback. Bug found live: deriving the reset key from the label reset the playhead when streaming finished — resetKey is now an explicit prop.
  • LiveView: seed prompts, free prompt, model picker, run/cancel/retry/ reset, status line, agent-offline panel (with the exact command to start the local agent), and "download fixture" — a completed live run exports as a session fixture (the third event source, already round-trippable).
  • packages/scenarios: scenarios as data ({intent, seedPrompts, breakItPrompts, fixtures, status}). "project-deletion" is the ready reference scenario (3 live fixtures); onboarding / support-triage / appointment-booking / recipe-creator / hotel-reservations ship as status:"planned" entries stating what they wait on (contract expansion is owner-authored governance content). The studio shell is fully scenario-driven: shelf -> replay | live | themes; zero per-scenario UI.
  • Playwright grew live-mode coverage (scripted adapter, deterministic): stream->render->outcome->scrub, run-again, reset+download. Config runs two webServers (static export + agent). 9/9, run twice for stability; two non-waiting count assertions replaced with auto-waiting ones.

Verified live in-browser (not just tests): a real ollama:gpt-oss run from the Run button — 2 -> 6 -> 16 events streaming progressively, an S3 catch + repair mid-run, final surface rendered, then scrubbed backward; cancel mid-stream -> "cancelled" + retry offered.

Validation (2026-07-10): unit 6/6 + 5/5; typecheck OK x7 packages/apps; contract gates clean; static export builds; Playwright 9/9 twice.

Session import + HITL + shared state + appointment booking (2026-07-10)

Pushed: origin = github.com/aestheticfunction/dspack-studio, main tracks origin/main.

Session import (third event source, closed):

  • importFixture in packages/replay: strict user-facing validation — 5 MB / 5000-event caps, version check (0.1 only), per-event shape check, mode whitelist. 4 unit tests + 2 e2e (malformed JSON, future version).
  • Replay pane: file picker + drag-and-drop; imported sessions labeled "(imported)" with provenance (recordedAt, prompt); identical RunView path.
  • Round-trip proven by e2e: run live -> download -> import -> scrub to the same state.

HITL action round-trips:

  • STUDIO_EVENT namespace (studio.action.pending/accepted/rejected/cancelled/ failed) separate from dspack.* pipeline events; every state carries the same correlation actionId. UI events, agent events, and pipeline events are distinct namespaces in one ordered stream — replay-safe by construction.
  • Client: sendAction (dedupe per name+source while in flight; UUID correlation; failure appends studio.action.failed; retry = re-send). Server: POST /action, idempotent by actionId (duplicates return the original response), scenario-neutral responder registry.
  • Acting on the surface re-attaches the live follow (found by e2e: after scrubbing, a canvas action must snap the playhead back to now).

Shared-state co-editing (public mechanisms only):

  • A2UI updateDataModel ops ({surfaceId, path, value}) = partial updates, ordered by the event stream; user input is optimistic-local via the binder's generated setters (TextFieldRender uses props.setValue when bound); an ACCEPTED action commits its submitted values back into the data model (sync-on-action per the A2UI spec) — that commit is what survives replay/reconstruction. Conflicts: agent-authoritative on action (last-committed-wins), validation rejections update /booking/status and ride studio.action.rejected verbatim.

Appointment booking (first interactive scenario):

  • Structure from the contract: surfaces/appointment-booking.dsurface.json, emitted by dspack-emit at build (unscoped rules apply; note the emitter slugifies ids: name-input -> name_input).
  • The agent adds the interaction OVERLAY (bindings + named actions with context paths) — exactly the layer dspack v0.4's Deliberate Ceiling declares out of contract scope; documented as evidence.
  • State schema /booking/{name,slot,status,confirmed}; actions select_slot / confirm_booking / cancel_booking; deterministic responder (no provider, no model). Full arc verified in-browser AND by e2e: validation rejection, hold with committed name, confirm -> "Booked 10:30 for Ada", scrub-back reconstruction, start over.
  • Rendering discovery: the published @a2ui/react renderer memoizes resolved props per surface — bound-value updates require remounting the canvas per delivery (keyed on ops count). A transient mid-edit compile error also produced a stale-bundle debugging detour; verify compile state first.

Governance boundary (owner decision needed for the LIVE-generation path):

  • The contract has no scheduling intent, so run it live with a model is gated for booking (deterministic start + HITL work without it). Proposed draft for owner review: intents += { id: "scheduling", name: "Scheduling", description: "Surfaces that collect a time choice and confirm it." } examples += ex.book-consultation (the authored booking surface, verbatim); optional rule: required-composition — a scheduling surface must carry at least one button (the confirm affordance). Compatibility: additive (v0.4-safe); no existing rules change; fewshot grows by one example.

Validation (2026-07-10): frozen-lockfile install clean; unit 6/9/5; typecheck OK x7; contract gates clean; drift-check clean (1 unverifiable alias); static export builds; Playwright 13/13 twice (replay 6, live 3, interactive+import 4).

Follow-ups:

  • Upstream (dspack-gen): export PipelineEvent (+ consider GateReport) from the package root.
  • Upstream (contract, owner decision): intents/rules/examples for the five planned scenarios; ceiling note for degenerate empty text-in-text chains.
  • Load a downloaded session fixture back into the replay view (file-open) — the format already round-trips.
  • HITL + STATE_DELTA co-editing (appointment-booking / recipe-creator prerequisites).

Scheduling governance wired through (2026-07-10)

Owner-approved, additive: intent scheduling + worked example ex.book-consultation (the authored booking surface, verbatim) added to the AUTHORITATIVE contract (dspack repo, branch claude/agitated-lovelace-016480 — which also now carries the previously-uncommitted v0.1.4 drift fix as its own commit). Copies propagated to dspack-emit/input and packages/contracts. NOTE: dspack-gen's check:sync gates against GitHub main — it will fail upstream until the branch merges (owner PR).

Rule hypothesis (documented, NOT added, per owner decision):

  • Intended failure mode: scheduling surfaces that collect a time but give the user no way to commit it (dead-end forms).
  • Would correctly reject: a slot list with no confirm affordance; a name field + slots with only decorative text.
  • Could incorrectly reject: availability browsing, reschedule/cancel views, "no slots available" informational states, read-only calendar summaries.
  • Narrower candidate: apply only to a terminal booking-confirmation intent (e.g. scheduling-confirmation) or gate on a confirmed-writing action being reachable — needs eval evidence first.

Validation of the change: scheduling context compiles (fewshot=2, includes booking); example lints S1/S2/S3 PASS; destructive-action context unchanged; both A2UI catalogs gate clean; drift-check clean.

Mini eval vs baseline: pre-change, scheduling generation was IMPOSSIBLE (no intent). Post-change: 3/3 live gpt-oss generations for "a screen to book a consultation time" passed all gates first-attempt (no refusals, no projection failures, no repairs). fixture-004 ("Scheduling, generated live", mode live) recorded. Full 216-run matrix rerun: queued follow-up.

Booking live path: interactive scenarios now offer BOTH "start scenario" (deterministic, the CI-stable fallback) and "generate live" (real pipeline under the scheduling intent); labels stay honest (adapterId per run). Finding: generated surfaces carry synthesized action slugs (not the select_slot overlay), so HITL responses reject them gracefully as unknown actions — wiring generated actions to responders is next-block work alongside the inspectors, recipe-creator, and the remount benchmark.

Generated-action resolution layer (2026-07-10, in progress)

PR verified: dspack#20 (OPEN) carries the drift fix + scheduling governance. Full eval matrix (P5) waits on its merge (check:sync gates on GitHub main).

Implemented (unit-tested, wired, committed):

  • packages/scenarios/capabilities.ts: scenario-neutral resolution. Capabilities declare exact action names AND component-grounded matchers with VALIDATED semantics (e.g. select_slot grounds on a Button whose label parses as HH:MM — never bare label-string matching hidden in code). resolveAction(action, surfaceComponents, capabilities) is pure -> deterministic in replay; preserves the original (synthesized) identifier as provenance; single-hit resolves, multi-hit -> "ambiguous", no-hit -> "unsupported" (both rejected clearly, never guessed). 5/5 tests incl. synthesized slugs, ambiguity, determinism.
  • replay: surfaceComponentsAt reducer (latest component defs at playhead).
  • Client: LiveView resolves before dispatch (works for deterministic AND generated surfaces); studio.action.resolved/unresolved recorded in the stream with method + provenance; unresolved actions never POST.
  • Agent: /action dispatches on capability ?? name (idempotency unchanged).

Remaining in this block (next session): e2e verification of a generated scheduling interaction + fixture-005 recording; progressive-disclosure inspectors (P2); recipe-creator foundation + governance proposal (P3); canvas-remount benchmark (P4); full matrix after dspack#20 merges (P5).

P1 continued (2026-07-10): generated-surface enhancement shipped — enhanceGeneratedOps grounds ONLY unambiguous components (single TextField -> /booking/name, single caption Text -> /booking/status, time-token-labeled Buttons -> select_slot with context paths, single primary -> confirm, single ghost -> cancel), every attachment recorded via CUSTOM studio.surface.enhanced. Slot semantic widened to labels containing exactly one HH:MM token (gpt-oss generates "Mon 9:00"-style labels); the same extraction (slotFromLabel) is shared by client matchers and agent enhancement. Verified in-browser: a generated scheduling surface rendered with enhanced bindings and the initial data model ("Pick a time to begin." via binding). A second generated run hit the emitter-refusal class (exit 3, natural variance) and the failure panel handled it as designed. Pending next session: full generated-interaction happy path in-browser + Playwright, fixture-005 recording, then P2 inspectors, P3 recipe proposal, P4 remount benchmark, P5 matrix (blocked on dspack#20 merge).

P1 sign-off, inspectors, recipe foundation, benchmark (2026-07-10)

Merge verified: dspack/main = 86dbe85 (PR #20; drift fix 9857b85 + scheduling 7085835). dspack-gen check:sync re-synced (--write) with FA goldens regenerated deliberately (rationale strings now cite v0.1.4); 93/93; committed on branch sync/astryx-scheduling-contract. All three contract copies verified byte-identical to GitHub main.

P1 SIGNED OFF:

  • Scripted adapter is intent-aware (plays the worked example for the requested intent) — CI has a deterministic generated-booking path.
  • fixture-005 (mode live, ollama:gpt-oss:latest, 27 events, 9.5s): generation under the merged scheduling intent -> gates -> studio.surface.enhanced provenance -> resolved->pending->accepted twice (slot, confirm) -> /booking/confirmed=true, "Booked Mon 9:00 AM for Ada". Recorded via the gated RECORD_LIVE Playwright spec (retry budget 4; first recording attempt failed on ESM __dirname — fixed to process.cwd()). Registered as the booking scenario's replay fixture; replayable in-app.
  • e2e: deterministic generated path (pipeline -> enhancement -> semantic resolution -> confirm -> scrub reconstruction incl. committed name).

P2 INSPECTORS: closed-by-default disclosure inside RunView; tabs state (model + ordered patch log with delivery correlation), actions (lifecycles by correlation id, resolution method, rejection detail), events (category-colored: run/step/pipeline/a2ui/user-action/agent-response/ enhancement), a2ui ops, gates (findings + repair messages + refusal), components (bindings ⇄ paths, grounded actions). All panels are prefix folds — synchronized with scrubbing by construction; 4 reducer unit tests assert no-future-state; 4 e2e tests assert backward/forward sync against fixture-005. Identical across live, replayed, imported sessions.

P3 RECIPE FOUNDATION (deterministic; governance proposal below, not applied):

  • Authored contract surface (surfaces/recipe-creator.dsurface.json, intent structured-editing) emitted via the generalized surfaces/ build loop.
  • Agent module: servings rescale the ingredients Table via updateComponents (component-level co-editing beyond DM patches), constraints validate against a known set (unknown -> recoverable rejection), regenerate cycles deterministic variants preserving servings+constraint; per-session state cleared on scenario start (first suite run exposed cross-run session leakage — fixed). 4 unit tests; 3 e2e tests incl. export.
  • Scenario-neutral shell: SCENARIOS registry (start + respond) in the agent; capabilitiesByScenario in the scenarios package; live view reads both. "generate live" is hidden for scenarios with unmet governance needs.

P4 BENCHMARK (measured, then one adapter-level fix, no renderer changes):

  • 8 rapid interactive deliveries: click->paint mean 39-45ms; total processor time 3.4ms (≈0.1ms/rebuild) — processing cost is a non-issue at this scale.
  • Found 32 rebuilds for 8 deliveries (array-identity effect deps); fixed by keying the processor effect on messages.length (a prefix accumulator ⇒ equal length = identical content): now exactly 1 rebuild per delivery.
  • The real user-facing cost is FOCUS + DRAFT-INPUT LOSS on delivery (focusRetained=false, draftRetained=false): inherent to the remount workaround for @a2ui/react's per-surface memoization of resolved props. Verdict: acceptable for scheduling and recipe (typing precedes actions; agent patches land after actions, not mid-keystroke). A real fix means upstream reactive re-resolution in @a2ui/react — filed as a follow-up, explicitly NOT fork-worthy on this evidence.

P5 MATRIX: running (216 live runs, json-render target, byte-comparable to the m3 baseline 67/216; ~14 runs in, gemma segment). Note the matrix prompts are all destructive-action — it measures regression on the unrelated intent (the fewshot corpus now includes ex.book-consultation cross-intent); scheduling-specific numbers come from the recorded live runs and a supplementary prompt set if regression review warrants it.

Validation: frozen install clean; unit 6/13/5/9; typecheck OK x7; contract gates + drift clean; export builds; Playwright 21/21 twice (+2 gated).

Break-it Mode, renderer abstraction, distributed eval (2026-07-10)

Ecosystem sync PRs merged upstream (dspack-gen#39, dspack-emit#19); check:sync green on dspack-gen main.

DISTRIBUTED EVAL (zero framework changes): the 216-run matrix now executes on two hosts sharing one outDir — the local worker in matrix order, a remote worker (OLLAMA_HOST=, model digests verified identical) walking a reversed-model-order COPY of the same matrix from the far end. --resume treats retained per-cell reports as completed observations, so the partitions converge without coordination; the canonical results.json comes from a final --resume pass over the ORIGINAL matrix file (identical prompts, models, scoring, aggregation, and report ordering). Host attribution = per-process logs (/tmp/eval-matrix{,-remote}.log), preserved with the run config. Second offered host (host-2) unreachable — documented. Duplicate execution is possible only for cells in flight at the partition meeting point (wasted compute, never duplicate observations: one retained report per cell run). No methodology change; no runner redesign.

BREAK-IT MODE (FM-8): breakConditions data (7 curated conditions: no-alertdialog, OK-label, unsupported component, malformed generation, ungroundable action, invalid shared state, malformed import) + agent-side BREAK_SCRIPTS (authored violating->repaired surface pairs played through the ORDINARY pipeline by the scripted adapter — deterministic, CI-safe, labeled scripted; live-model variant uses the same visitor-typable prompt). BreakView tab: pick a condition, read the expected outcome, run it, watch the gates/ repair/refusal in the same RunView + inspectors; replay/scrub/export inherited. 6 e2e tests (27 total passing).

RENDERER ABSTRACTION: docs/renderer-abstraction.md states the verified boundaries (AG-UI -> A2UI -> AF contract -> renderer adapter -> design system), interfaces (Registry, buildCatalog, render contract), capability discovery (BuiltCatalog.names/unimplemented), unsupported-component behavior, theme ownership (design-system layer only), and the exact swap requirements. Validated by registry-abstraction.test.ts: a minimal plain- HTML alternate registry against the REAL emitted catalog proves the catalog owns vocabulary/schemas and a registry cannot widen or narrow them. No migration performed.

Validation: frozen install; unit 3/6/13/5/9; typecheck x8; contract gates + drift clean; check:sync green; export builds; Playwright 27 passed (+2 gated).

Host-2 probe (2026-07-10, per owner instruction): host-2 reachable; Ollama 0.31.1; model digests identical to local + host-1 (gemma4:e4b c6eb396dbd59, qwen3.6:35b 07d35212591f, gpt-oss 17052f91a42e). Throughput probe (gemma, fixed 120-token budget): 2.6 tok/s vs 33.8 tok/s on host-1 — CPU-only in practice (CUDA_VISIBLE_DEVICES=-1; GPUs held by VLLM), 13x slower on the smallest model. Remaining matrix cells are qwen-35B + gpt-oss only — the worst fit for CPU. DECISION: left unused (contribution would be negative wall-clock); no evaluation cells assigned; methodology unchanged.

Launch-readiness block (2026-07-10)

Matrix untouched throughout (isolated in dspack-gen/out/eval/...; no eval files modified). Progress checked without interruption.

CI: .github/workflows/ci.yml — frozen install, contract gates, authoritative sync check (curl diff vs dspack main), Astryx drift check (local CLI, no network), all unit suites, typechecks, static export, Playwright against the deploy artifact (deterministic paths only; RECORD_LIVE/BENCH specs skip); pnpm + Playwright-browser caching; report artifact on failure.

Deployment: docs/deployment.md (launch topology = static-only export to Cloudflare Pages; live topology optional/owner-gated; health checks; rollback = redeploy prior immutable artifact; agent stateless). .env.example placeholders only. Agent CORS hardened: AGENT_ALLOWED_ORIGINS allowlist (dev default *), origin echoed only when allowed, vary: origin. Verified no private hosts/keys in the client bundle (browser sees model refs only).

Accessibility: axe (wcag2a/2aa) e2e suite + keyboard-operability test. Real defects found and fixed: planned-chip contrast 3.36:1 -> 0.62 opacity; inspector event-category and gate-status text palettes darkened to >=4.5:1; tablist contained non-tab children (restructured); model select lacked an accessible name; timeline ticks gained aria-labels; status/audit/canvas-empty regions aria-live=polite. Automation is the floor: manual keyboard/SR passes remain on the release checklist.

Performance (production artifact): first-load shell 103 kB; the app chunk (Astryx + A2UI + all fixtures) is 2.1 MB uncompressed loaded async (fixtures 148 KB total); export 3.6 MB. Verdict: no optimization warranted at these numbers; import caps (5 MB / 5000 events) bound large-session behavior; repeated replay/reset and agent-disconnect recovery are exercised by the e2e suite. No renderer or event-architecture changes.

Docs: CONTRIBUTING.md (setup, tests, fixture recording + provenance, adding scenarios, governance boundary, mermaid architecture diagram, known limitations incl. focus-loss ceiling, troubleshooting incl. the .next corruption trap); README already MVP-focused; docs/release-checklist.md.

Validation: Playwright 29 passed (+2 gated) incl. 4 axe + keyboard tests.

216-run matrix complete (2026-07-10) — authoritative evaluation

Config preserved in dspack-gen/out/eval/scheduling-2026-07-10/ (results.json with matrixSha256 a369d0d4… and contract sha e6c1632f… [dspack 0.4, Astryx], canonical + execution-order matrix copies, both worker logs, all 216 retained audit reports). dspack-gen at 07774f6; json-render target — byte-comparable methodology to the m3 baseline (67/216 on the v0.1.2 contract).

Headline: 192/216 passed (88.9%) vs baseline 67/216 (31.0%). First-attempt pass 141/216 (65.3%); repair success 51/67 (76%); ZERO emitter-gate failures and ZERO s3-clean-but-refused (m3 had 7 residual — the ADR-D1 class is extinct in this configuration); 16 lint-exhausted; 8 failed-adapter (qwen transport, infra class). Per model (passed/72): gemma 64 (was 17), qwen 59 (was 9, incl. the 8 infra failures), gpt-oss 69 (was 41). Top rule fired on first attempts: destructive-requires-alertdialog (61) — the rules still catch; models now repair. Worst prompt a01-delete-project 12/18.

Attribution honesty: the local worker predated --resume and executed all 216 runs; the remote GPU worker independently executed ~97 overlapping cells (reversed order, --resume) — each retained report is one valid observation of the exact cell config; per-cell host provenance is a documented mix. Distributed execution changed nothing methodological (same matrix, prompts, models, scoring, aggregation; canonical pass over the original file).

Interpretation limits: the delta vs m3 conflates (a) the v0.1.4 contract alignment (text.type semantics) and (b) the added cross-intent worked example — not separable without an ablation run. What IS clean: adding ex.book-consultation did not regress the destructive intent (it improved 3x alongside the drift fix), scheduling generation went from impossible to first-attempt-clean in targeted runs, and no evidence supports any scheduling rule (zero scheduling failures to justify one).

Recipe governance applied + live path (2026-07-10, in progress)

Owner approved structured-editing + ex.recipe-creator (no rule). Applied to the authoritative dspack contract on branch feat/structured-editing-intent — PR #21 (aebecb4 + 3d6819d). Validated: example lints S1/S2/S3 clean; few-shot contexts are INTENT-SCOPED (verified) — a correction to earlier reporting: adding examples never changes other intents' contexts, so the matrix improvement attributes to the v0.1.4 alignment, not few-shot growth. Studio copy propagated byte-identical to the branch; both catalogs gate clean. Studio CI's sync check stays red until PR #21 merges (expected). dspack-gen/dspack-emit sync PRs follow the merge (established chain).

Live recipe generation: enabled under structured-editing; scenario registry now carries per-scenario enhance hooks (booking + recipe); recipe enhancement grounds only the unambiguous (single TextField -> /recipe/constraint, single caption -> /recipe/status, single primary -> regenerate, single secondary -> apply_constraint) — servings buttons stay ungrounded by design (no validated semantic for +/- deltas; resolution rejects them clearly). Deterministic path unchanged (CI fallback, offline mode, curated demo, comparison baseline) — same state/capabilities/ transport/rendering/timeline/inspectors/replay/export.

fixture-006: BLOCKED on a measured signature. After the worked example's empty table was populated with literal rows (teachability fix, amended into PR #21 — generated tables then carried correct columns/data shapes), gpt-oss still emits table-as-container (rows as child nodes) in 6+ consecutive runs -> emitter refusal "component 'table' has children but its surface plan declares no child slot". Same class as the Text-children finding (measured, systematic, profile capability gap — Astryx Table natively supports a children mode via TableRow/TableCell). NEXT STEP: synthesize TableRow/ TableCell catalog primitives + Table childrenProp in the profile with matching renderers, then record fixture-006. Retries are not the fix.

Queued in this block: fixture-006 (after the Table capability), FM-9 Wire View, FM-3 Fork UX, matrix archive doc.

fixture-006 root-cause investigation (2026-07-10, evening)

The table-children capability landed (profile: catalog Table gained an optional children ChildList; renderer: Astryx Table children mode via TableRow/TableCell, flat children chunked into rows of one cell per column) and eliminated the emitter-refusal class — but recordings still failed: generated surfaces were minimal 3-6-node prefixes, text-less, table-less. The investigation, in order of falsified hypotheses:

  1. Context-window overflow (Ollama default 4096) — falsified: raising num_ctx/num_predict per request changed nothing; done_reason was "stop", not truncation. (The num_ctx raise is kept as headroom.)
  2. Pipeline-vs-probe difference — falsified byte-for-byte: captured the adapter's outgoing body and diffed against a hand-built request; identical. It was sampling from one distribution all along.
  3. Read the model's reasoning: gpt-oss PLANS the full rich surface (title text, populated table rows, constraint input, regenerate) and the constrained output then omits exactly what the grammar forbids.

Root cause, twofold, in dspack-gen's generation schema (grammar- constrained decoders enforce declared shapes and property order):

  • Array-typed contract props fell through to { type: "string" } — the grammar FORBADE the arrays the model plans (table columns/data); forced deviations derailed generation right where the table starts.
  • Node properties declared text BEFORE props, while models and the worked examples serialize props first — node text was unreachable once props was emitted (text-less nodes in every live generation).

Fixes, each at its owning layer:

  • dspack-gen PR #40 (0.1.1): array props emit as arrays with optional contract-declared items passthrough; property order is component, id, props, text, children. 93/93 tests, golden regenerated.
  • dspack PR #21 (amended): table columns/data items typed (strings / { cells: string[] } records — matching the approved example and the A2UI projection; the old description claimed [{key, header}] objects the example never used). An earlier same-day example-reorder commit went the WRONG direction and is reverted on the branch.
  • Studio: enhancer grounds apply_constraint by validated label semantics (exactly one non-primary button labeled ~constraint — the slotFromLabel precedent); "single secondary" could never ground on example-mirroring surfaces (the example itself has three). Measured end-to-end with local fixed source + typed contract: rich runs carry typed columns and node text (probe: 18 nodes, 10 textful); the minimal mode persists at ~50% but 4 recorder retries cover it (~94%).

BLOCKED: recording fixture-006 waits for dspack-gen 0.1.1 on npm (the flagship proves the published packages; recording against unpublished source would break the audit's schemaSha256 provenance). FM-9/FM-3 proceed meanwhile — replay-layer work, independent of generation.

FM-9 Wire View + FM-3 Fork UX (2026-07-10, late)

FM-9: wire-view.tsx renders which of the seven pipeline layers (you / agent / projection / AG-UI / A2UI / registry / Astryx) the PLAYHEAD event actually touches — a pure function of the current event, no parallel telemetry. Uninvolved stages dim (a gate verdict lights two layers; only the delivery crosses six; user actions flow the other direction with flipped arrows). Correlation ids (toolCallId, surfaceId, actionId) render as chips. Closed by default. e2e: e2e/wire.spec.ts. The a11y sweep it triggered fixed keyboard focus on every scrollable inspector region and labeled the break-prompt input (axe clean again).

FM-3 v1: forkFixture/unforkableReason in packages/replay — a fork is a NEW run (own id) whose events are a deep copy of the parent's prefix [0..playhead], with explicit ForkProvenance {parentId, parentName, forkIndex, forkedAt} (additive 0.1-compatible field). The parent is never mutated (test-proven: appending to or editing the fork leaves the source byte-identical). Moments before any delivered surface are rejected with the reason stated ("no application state to diverge from"). UI: a fork button beside the timeline (disabled with the reason until forkable), fork chips on the replay shelf, provenance blurb, download; a downloaded fork reopens through the normal session import with provenance intact (e2e/fork.spec.ts, incl. the export->import round-trip). Deliberately v1: forked runs are scrubbable/inspectable/exportable diverging copies; CONTINUING a fork with new HITL actions needs the agent's session state rebuilt from the prefix (replaying accepted actions through the deterministic responders) — designed, not yet wired; live re-generation forks are the BYO-live milestone after that.

Validation: 31 unit tests + agent 9/9 + 36 Playwright (3 gated skips).

0.1.1 propagation, fixture-006, FM-3 continuation (2026-07-11) — COMPLETE

Release verified before anything downstream moved: dspack PR #21 merge on main (fe4b8df); dspack-gen v0.1.1 release workflow green after the repair (trusted publishing registration + npm >= 11.5 via Node 24 — the first two runs failed E404 on the unauthenticated PUT; nothing partially published); registry artifact shasum byte-identical to the CI tarball, SLSA v1 provenance verified via npm audit signatures, schema fixes confirmed from a fresh registry install.

Propagation: studio consumes ^0.1.1 (lockfile updated, artifacts regenerated, both A2UI versions gate clean, contract copy byte-identical to dspack main); dspack-emit sync PR #20 (no golden changes — 55/55 pass; its own built-in astryx a2ui MAPPING's unclassified text/text-input is pre-existing and not CI-gated, tracked as a finding); dspack-gen PR #42 adds grammar-alignment regressions (8 tests; 5 fail under the 0.1.0 schema builder, including the ex.recipe-creator oracle).

fixture-006 (mode live, gpt-oss, 27 events, ~12s, first-attempt pass, 0 repairs): generated recipe surface WITH node text (the 0.1.1 fix visible in the artifact), enhancement grounds input/status/apply/regenerate and RECORDS its grounded update targets in the stream; apply_constraint 'vegetarian' lands 4 rows on the rendered generated table; regenerate cycles the dish. On the shelf as "generated, then co-edited"; fidelity proven by e2e (shelf replay, scrub reconstruction at three moments, export->import round-trip). Recording found a real defect: responder co-edits orphaned on authored ids for generated surfaces — fixed by enhancement-time retargeting (unambiguous slots only), agent-tested.

FM-3 deterministic continuation: POST /fork rebuilds scenario state from a fork's prefix (reset + grounding restore + accepted-action replay through the deterministic responders; divergent replays are refused 409, nothing invented). The replay pane continues forks live: new actions append ONLY to the branch, the parent stays untouched, continued forks export with provenance + branch events and reopen through session import. e2e proves the money shot: parent applied vegetarian; its fork applies vegan; both render their own truths. Action dispatch extracted to one shared module (live view + continuation use identical wire shapes). Live-model continuation remains gated/manual — the deterministic path proves the architecture.

Validation: frozen-lockfile install; typecheck clean; 51 unit tests; 6/6 A2UI gates; sync + Astryx drift clean; static export; 43 Playwright (3 gated live/bench skips) including new wire-view, fixture-006, fork-continue, and fork/wire a11y coverage (contrast fixes on the new controls). Deterministic CI stays Ollama-independent.

Plan-conformance block: FM-12, permalinks, tour, X-ray, the wire, branch compare (2026-07-11) — COMPLETE

Merged-state verified first (emit PR #20, gen PR #42 on their mains, CI green, all sync copies byte-identical, studio main clean).

Plan-conformance review found five user-facing gaps behind the plumbing (a reducer or inspector tab is not a moment): FM-12 was display-only, no permalinks, no tour, FM-4 was an inspector tab rather than a canvas interaction, and FM-9's stage-flow panel was not the raw wire. All five closed this block:

  • FM-12: receipt.ts in packages/replay — receiptVersion 1, canonical byte-match boundary defined precisely (stable-stringify of {intent, prompt, report} MINUS report.createdAt and report.timings; session identity displayed but unhashed), sha256 anchor, verifyReceipt with match / MISMATCH / invalid(reason) — never silent. ReceiptView panel: attempts, findings, repairs, warnings, gates per A2UI version, adapter, contract digest, schema hash, provenance, download, verify-a-file. e2e proves the downloaded hash equals an independent recomputation from the fixture file, and cross-run receipts mismatch loudly.
  • Permalinks: hash-based (static-safe) #s=&f=&e=&panel=&x= plus fork=parentKey@index (forks of bundled fixtures reconstruct deterministically; imported sessions are deliberately not linkable and the UI says so). Copy-link writes the URL; reload reconstructs scenario, recording, moment, x-ray, and open panel; malformed/unknown links fail with the reason and fall back to a working default.
  • Guided tour: four steps on fixture-001 (argue-back moment, time travel, x-ray, receipt) driving the SAME deep-link mechanism as permalinks — every step is a reachable real state; dismissible, restartable, keyboard-operable, motion-free by construction, never blocks.
  • FM-4 X-ray: canvas interaction — toggle, click any rendered node, provenance card (A2UI id, catalog/contract component, renderer, creating/updating events with timeline jumps, marked EXPLICIT) and rule findings matched by component type (marked INFERRED — findings cite surface locations, not node ids); reverse direction: a delivery event at the playhead washes its components violet.
  • FM-9 resolved into two honest panels: the stage-flow visualization is now the Pipeline View (renamed, same behavior), and THE WIRE is new: raw ordered AG-UI events with expandable JSON, the real content types (recorded fixture = JSON document; live transport text/event-stream), and per-event protobuf frames explicitly labeled as a RE-ENCODED VIEW of the same events (bridge gained encodeEventBinary).
  • FM-3 branch compare: original vs fork side by side at their own endings (events, component counts, final data models, disagreeing state paths); two branches plainly labeled, no graph.

RC pass: alive by default (the homepage recording auto-plays; reduced motion lands on the finished surface instead — Playwright emulates reduced motion so CI stays deterministic), scope-boundary footer (open ecosystem only; proprietary reconciliation not included), em-dash sweep of public copy, controls wrap on narrow viewports (375px verified).

Validation: frozen lockfile, typecheck clean, 55 unit tests (45 pkg + 10 agent), 6/6 A2UI gates, drift + sync clean, static export, 56 Playwright + 3 gated skips (incl. new receipts, permalinks, tour/x-ray/wire, alive, a11y).

Still deferred by plan/owner: FM-10 swap, FM-11 take-it-home, new scenarios, hosted inference, A2UI 1.0 renderer, OG cards + analytics + domain (owner-gated launch items), conversation-rail narration (the pipeline emits no TEXT_MESSAGE; synthesizing narration would violate honest-magic — gate ticker + finding cards + tour carry the story).

2026-07-11 — Audit + product-experience pass (comprehension, honesty, recipe)

Full plan-vs-implementation audit recorded in docs/AUDIT.md (new). The implementation was found substantially complete and honest; this pass fixed the comprehension and availability gaps a first-time visitor hits, and closed the recipe-content definition-of-done item.

  • Governance-first story: the hero now leads with "The design system governs what the agent ships" and the plain-language loop (propose, check, explain, repair, replay); protocol names moved to How-it-works. One-sentence helper line under the view switcher, per view (view-help); "worked example + themes" renamed "restyle it"; FM-5 caption under the themed artboard.
  • How-it-works diagram: the AF .dgm idiom (six node cards over the green bus SVG, dspack node green-ruled) transcribed from af-site; static, text-first, collapses to a 2-up grid without the bus under 640px.
  • Availability honesty: one-shot agent probe in the shell; live/break switcher buttons carry an offline mark; Break-it Mode without the agent replays the recorded catch for no-alertdialog / ok-label (fixture-001 ran that exact prompt and repaired both rules) and unsupported-component (fixture-003's verbatim refusal), and labels the three live-only conditions plainly. Nothing dead-ends silently on the static deploy.
  • Recipe instructions end to end: VARIANTS carry numbered steps whose ingredient names the constraint swaps rewrite (unit-tested); the authored dsurface gained instructions nodes (contract file untouched — it byte-syncs with dspack main; the worked example there is owner-authored governance content); the enhancer disambiguates tables by column names, appends a labeled instructions table when a model omits one, and seeds title/ingredients/instructions at enhancement so a generated surface is a full recipe from its first frame. fixture-006 re-recorded live (ollama:gpt-oss:latest, 27 events, same structure: enhanced@10, accepted@19) — the model laid out both tables itself.
  • Shelf a11y: planned scenarios are real buttons that reveal their blockers inline (planned-needs) on tap/keyboard; x-ray gained a keyboard path (trace buttons in the inspector components tab); .st-cols-2 responsive collapse for branch compare; main padding tightens under 640px.
  • Cleanup: packages/provenance stub deleted (zero reverse deps; the real provenance lives in astryx-renderers + replay); README reflects the governance-first story and the new behaviors.
  • Tests: e2e workers pinned to 1 (the local agent is deliberately single-session; parallel workers raced its grounding — observed as an intermittent recipe/fork-continue collision). New specs: studio-shell.spec.ts (hero order, view-help, diagram, planned reveal, FM-5, x-ray trace) and break-offline.spec.ts (agent blocked at the network layer; recorded catches, live-only labels, client-side import demo) — both also registered in the production suite.

2026-07-11 — Recipe table layout fix + scenario shelf reorder

Two follow-ups from a user report on the deployed recipe.

  • Recipe table overlap: Astryx's table scroll wrapper carries a -16px margin on all four sides (full-bleed for a lone table in a padded Card). In a Column (VStack gap 12px) two stacked tables computed 12 - 16 - 16 = -20px and overlapped (the recipe's ingredients and instructions tables). globals.css now zeroes only the vertical bleed on table scroll wrappers within [data-canvas], keeping the horizontal full-bleed; the tables stack with the 12px gap. Root-caused in the browser (measured the negative margins), not guessed.
  • Scenario shelf order: ready scenarios lead (recipe co-editing is now the default, then appointment booking, then project deletion), planned ones grouped after. readyScenarios[0] drives the default, so the reorder alone changes it. Eight e2e specs implicitly assumed project-deletion was the default (they used its recordings, gates, and event indices without selecting it); each now selects scenario-project-deletion explicitly — a more robust coupling. fork.spec waits for the selected scenario's autoplay to settle before manipulating the timeline (the new-scenario mount raced an immediate Home press).

Validation: typecheck, 49 unit, build:deploy, 69 Playwright + 3 gated skips, all green.

2026-07-14 — PHASE-NEXT execution: WS0 re-pin, FM-11, FM-10 groundwork, FM-7

The four workstreams of docs/PHASE-NEXT.md, shipped as separate PRs per the delivery discipline (fail-first evidence in each PR body).

  • WS0 (ds-mcp repo, PR #21): vendored core re-pinned f651433 -> 374e1cd (the dspack-gen v0.1.1 tag commit; tag-pinned is the documented default). Golden-context test byte-compares get-generation-context against dspack-gen's compiler golden — byte-level ON PURPOSE: the pre-0.1.1 divergence is property ORDER (text before props at schema line 40), which grammar decoders enforce and deepEqual cannot see. Pin-drift CI check compares src/core blob-exact via the git trees API (the compare API caps at 300 files and silently truncates — found by testing the check against the old pin, 698-file diff). Trusted-publishing release workflow added; 0.3.1 release + npm trusted-publisher registration are owner steps.
  • FM-11 (studio PR #13): the validate-ui spike CONFIRMED client-side execution — dspack-gen /core bundles into the static export and lintSurface runs in-browser against the byte-synced contract (first-load unchanged). Take-home view: MCP config (ds-mcp ^0.3.1 only; merge waits on the 0.3.1 release), byte-exact contract download (build-time copy), the real validator with fixture-001's recorded attempt-0 as the loadable caught example (findings byte-match the recording, unit + e2e), and the clone-and-run local-agent path executed verbatim from a cold clone. The npx agent package remains an owner decision; copy deliberately does not reference it.
  • FM-10 groundwork (studio PR #14): packages/shadcn-renderers (vendored shadcn/ui zinc visuals, Tailwind v4 compiled package-locally, no preflight, tokens scoped to the canvas; 8 of 9 names — Dialog exercises the unimplemented placeholder). withProvenance/childIds promoted to a2ui-ingest. Design-system selector in the restyle view, shell-level; every canvas renders through the selected registry. Second contract copy (shadcn-ui.dspack.json, byte-synced) + gated catalog.shadcn.* emission via dspack-emit's shadcnProfile. The e2e honesty check replays fixture-001 under both systems: receipt hash IDENTICAL, AlertDialog markup different, the repaired "Delete Account" label survives. Owner-gated remainder: the upstream contract extension (intents beyond destructive-action) for scenario coverage under the shadcn contract.
  • FM-7 (this PR): the booking HITL question is a real governed surface. select_slot acceptance returns a question descriptor; the server runs it through the ORDINARY pipeline (scripted from the authored surface, or generated live when the session's modelRef is a model) and appends the run's events — gates, emission, audit — to the action exchange (run lifecycle stripped; the question is a segment). The question rides its OWN surface (scheduling_question, re-scoped at enhancement) beside the booking surface — deleteSurface removes exactly it on answer, so the underlying surface (authored or generated) is never replaced. Grounding is unambiguous-only with literal slot/name context; ungroundable or refused live generations fall back to the authored question through the same pipeline, stated in the stream (studio.question.fallback). The canvas now renders every live surface in creation order (the stream decides, not the canvas). The governance story tells itself: rule.alertdialog-action-label-specific forbids "OK"/"Confirm" on the question's own action.

Fail-first evidence: 6 new unit assertions failed on the old responder (missing question descriptor, no deleteSurface); the two updated interactive e2e specs failed on a clean origin/main worktree with "element(s) not found" for [data-a2ui-surface=scheduling_question]; both captured before implementation and pasted in the PR.

2026-07-21 — support-ticket triage: fourth ready scenario (record-collection)

The first scenario shipped since the plan's "new scenarios need owner-authored contract intents/rules" deferral, and the template for the next one: replay-first, non-interactive, zero per-scenario code anywhere (no agent SCENARIOS entry, no capabilities, no authored surface, no web UI) — a registry record, owner-authored governance, and two live recordings.

Contract (astryx.dspack.json): intent record-collection; rule rule.record-collection-requires-table (component-choice, must, rationale verbatim from the Astryx Table docs via npx astryx component Table --json); rule rule.table-carries-data (required-props: a data-driven table must carry columns and data); worked example ex.support-ticket-triage (badge strip above a compact tickets table — the contract's table is data-driven strings, so badges live beside it, never in cells). Status badges are deliberately example-steered, not ruled: the linter renders a should-severity miss as "Required component does not appear" under a passing gate — a contradiction — and Badge's own docs endorse plain text when no action is needed.

Recording (Spark, OLLAMA_HOST, ollama:gpt-oss:latest) surfaced a real generation bug: across ~20 live runs on two models the table's nested data rows were NEVER emitted (optional scalar props were), so every run shipped an empty table shell — and with the data rule active, runs exhausted repairs instead. Root cause upstream: dspack-gen's generation schema marked every prop optional, and grammar-constrained decoders skip optional heavy branches; a repair round cannot fix what the grammar never demands. Fix in dspack-gen (feat/required-props-grammar, targeting 0.1.2): prop descriptors may declare required: true, which reaches the props schema's required and makes props itself required on the node branch. The contract now flags table columns and data required. With the fix: fixture-011 (clean) is a first-pass five-row triage table with a status badge strip; fixture-010 (argues-back) is the flagship catch — the model obeys a prose-list prompt with a lone text node, S3 fails on the table rule, and the repaired surface ships the filled table. --require-repair rejected 3 compliant runs before a genuine violation landed (recorded as rejected, not distorted). The optional emitter-refusal class was attempted once honestly; the model built a clean table instead, so no third fixture ships.

Break-it: records-as-prose (governed-repair, recordedCatch → argues-back; no scriptedRef — break-scripts untouched). Docs: AUDIT count 4 ready / 2 planned; NAV-REFACTOR matrix row filled.

Fail-first evidence: pre-contract, recording with the new intent failed with getIntent's "intent 'record-collection' is not registered" (pasted in the PR); post-contract the scripted smoke plays the worked example clean. All four support-triage.spec.ts assertions failed on the old registry (shelf "(planned)", missing fixtures, permalink "unknown or not-yet-ready"); upstream, three new grammar-alignment assertions failed on dspack-gen 0.1.1. shadcn intent parity stays deferred to the standing owner-authored contract-extension item.

2026-07-21 — signup/onboarding: fifth ready scenario (data-collection)

Same template as support-triage: replay-first, zero per-scenario code, an atomic PR of governance + recordings + registry + tests + docs.

Honesty split, stated everywhere it matters: labels were ALWAYS enforced (rule.input-carries-label is universal, no appliesTo) — the scenario's old "Labeled inputs, enforced" tagline over-claimed and was rewritten. What the new intent adds is structural: rule.data-collection-requires-input-and-action (component-choice, must) — an ask without a field is not a form, and a form that cannot be submitted does not ship. No layout, field count, or form shape is mandated, deliberately: data collection is not equivalent to signup forms. Rationales quote the Astryx TextInput and Button docs verbatim.

Durability check (owner requirement: the intent must remain durable beyond the signup example): a non-signup probe — "request access to a private workspace: name, work email, team" — produced an honest form under the same intent and rules. It also produced, unforced, the scenario's refusal story: the model picked dropdown-menu for the team select, which lints clean but the astryx profile cannot project. That recording ships as the third fixture; it documents the vocabulary's real limit (no select), which is a component gap (the hotel-reservations blocker class), not an intent-design flaw.

Recording (Spark, ollama:gpt-oss:latest, dspack-gen 0.1.2 published): fixture-012 clean — first pass to a labeled name+email form with one primary action (close to the worked example's shape; the few-shot steers hard, noted honestly). fixture-013 argues-back — first --require-repair attempt: the model obeys the text-only prompt with a card and welcome text, S3 fails on the new rule (both missing components), repair ships a labeled email field and a "Join now" action. fixture-014 refusal — reproduced on the second roll (first was a clean pass, discarded).

Fail-first evidence: pre-contract recording fails with getIntent's "intent 'data-collection' is not registered" (pasted in the PR); the five onboarding.spec.ts tests fail on the old registry. studio-shell.spec.ts's planned-reveal test retargeted scenario-onboarding → scenario-hotel-reservations (the last planned entry) — a retarget, not a new assertion; it passes on both builds.

2026-07-21 — selection vocabulary + hotel reservations: the shelf completes

Two PRs, per the approved platform design: the vocabulary first (inert until governed), then the intent and the scenario that validates it, atomically.

Vocabulary (PR A): list, selectable-card, metadata-list — each a faithful projection of a real upstream Astryx component, none hotel-specific. metadata-list is data-driven ({label,value} items, grammar-required) because sub-components carry no props under grammar-constrained generation; the one deliberate upstream divergence is declared as x-drift on the prop, and drift-check now reports x-drift props as acknowledged divergences. Renderers landed in BOTH design systems (Dialog stays the sole shadcn placeholder, 11 of 12). The record-collection table rule's "widen to a choice set" caveat was revised honestly: choice sets are inexpressible (component-choice require is conjunctive); requireOneOf noted as candidate dspack-gen work.

Governance + scenario (PR B): intent transactional-review with five floor rules (options-in-a-collection with an explicit action; ≥2 alternatives per list; every option states attributes; metadata carries items; every option is named). Exact counts, upper bounds, and "exactly one primary action per option" are inexpressible in current rule types and remain prose guidance, stated in the rules' own rationales. The worked example is deliberately NOT a hotel — ex.plan-comparison, pricing plans — so the vocabulary's domain neutrality is exercised before any hotel copy exists.

Recording (Spark, ollama:gpt-oss:latest, published dspack-gen 0.1.2): the very first live run on the deepest composite yet (card → list → selectable-cards → metadata-lists) passed clean with populated attributes — the 0.1.2 required-props grammar mechanism carrying over. fixture-015 (clean): three named, badged hotels, each with a 3-item metadata list, one "Book now" action, first pass. fixture-016 (argues-back): asked for a single no-options recommendation, the model shipped a bare apology text node; rule.review-presents-options caught a surface with nothing to choose (three findings — list, selectable-card, button all absent) and the repair shipped two named hotels compared on their attributes. First --require-repair attempt, unforced.

Break-it: review-without-options (recordedCatch → argues-back). The planned-entry shelf machinery keeps its code path but nothing renders it: studio-shell's planned-reveal test became "six ready, no planned placeholders", and permalinks' not-ready-link test now uses an invented id. Docs: AUDIT Scenarios row goes Complete (6 ready / 0 planned).

Fail-first evidence: pre-contract, recording with the new intent failed with getIntent's "intent 'transactional-review' is not registered"; all four hotel-reservations.spec.ts tests and the rewritten studio-shell assertion failed on the pre-scenario build.

2026-07-21 — example-expansion milestone: close-out

Merged this milestone, in order: #16 (tour auto-start for first-time visitors + first-run/studio Playwright projects), #17 (support-triage, record-collection), #18 (signup/onboarding, data-collection), #19 (selection vocabulary: list, selectable-card, metadata-list; x-drift acknowledged-divergence lane in drift-check), #20 (transactional-review

  • hotel-reservations). Cross-repo: dspack-gen 0.1.2 published (contract-declared required props reach the constrained-decoding grammar; dspack-gen #43), consumed from npm.

Final verified state on main at 7db93d8: 6 of 6 scenarios ready, none planned; Astryx contract at 12 components / 6 intents / 14 rules / 6 worked examples; fixtures 001-016 (004 bundled-unreferenced, pre-existing); every scenario replay/permalink/X-ray/Break-It/Restyle capable with live-recorded fixtures and fail-first e2e in both suites. Gates at close: pnpm -r typecheck/test clean; contracts build:catalogs

  • drift-check clean; local Playwright 106 passed / 4 skipped; production config against the deploy artifact 86 passed; live agent runs verified for record-collection, data-collection, and transactional-review on the published generator.

Deliberate deferrals, now platform work (roadmap rewritten in docs/PHASE-NEXT.md): contract re-sync upstream to dspack/examples (the byte-sync constraint is currently unmet — the milestone authored governance in the studio copy through reviewed PRs while upstream still carries the 3-intent edition; P0), shadcn contract parity (8/1/5 vs 12/6/14; P1), transactional-review interactivity (P2), generator expressiveness ceilings (requireOneOf, cardinality, per-parent child grammars; P3), dropdown-menu item vocabulary (P4), search-class components (P5). Technical debt introduced: none identified beyond those declared deferrals.