Skip to content

feat(genui-b): closed-IR read-only /mcp/apps render surface (additive, sol-GO) - #272

Open
LamaSu wants to merge 17 commits into
masterfrom
feat/genui-b-closed-ir
Open

feat(genui-b): closed-IR read-only /mcp/apps render surface (additive, sol-GO)#272
LamaSu wants to merge 17 commits into
masterfrom
feat/genui-b-closed-ir

Conversation

@LamaSu

@LamaSu LamaSu commented Jul 17, 2026

Copy link
Copy Markdown
Owner

PCC On-Ramp — Wave 3: distribution (bake generative UI into the live agent pack)

Stacked PR: #226 (artifact registry) → #227 (kit) → this. Base is feat/onramp-kit; review/merge after #227.

Wave 3 flips distribution on: the moment agent-package.json v2.19 + the wave-2 kit deploy, every entry point (pack URL, MCP, skill, /start, context-pack) hands out the generated-dashboard behavior. Spec: pcc-onramp-spec.md §7, §9 files 10–13.

Files

  • 10 (new) scripts/update-agent-package-v2.19-generative-ui.mjs — idempotent versioned-update script (mirrors v2.13/v2.14). Adds 5 tools (save_dashboard, get_dashboard, search_dashboards, fork_dashboard, update_dashboard) mapping to the wave-1 artifact routes; adds the top-level ui field (kit_url, shell_url, manifest_schema, render_base: https://capability.network/a/, example_manifests[]); adds a 4th worked example (pizza+courier → recall-first → discover-and-ask → compose → emit manifest → save → share). Bumps version → 2.19.0. toolCount 249 → 254.
  • 11 (mod) scripts/polish-agent-package-claude-max.mjssystem_prompt gains "## Generating a dashboard (when the task needs a surface)" (when/recall-first/how/honesty/save-share) and "## Configure before you commit — discover options, then ask" (the operator-required discover(build/options)→present→confirm-price→commit rule), plus a Do-list bullet. Both scripts now stamp 2.19.0 and polish preserves the update script's 4th example, so the pack is correct in either run order.
  • 12 (mod) packages/mcp-server/src/index.ts (+ api.ts) — 5 pcc_ui_* thin HTTP wrappers matching the existing 63; FetchOptions.method gains PUT (the route is app.put).
  • 13 (mod) skills/pcc.md + routes/start.ts + routes/context-pack.ts — short pointers to the full teaching.

Verification (watched)

  • node scripts/update-agent-package-v2.19-generative-ui.mjs → 5 tools + ui + 4th example + v2.19.0; run again → empty diff (byte-identical, idempotent).
  • Pack parses; all 5 tools have valid input_schema + endpoint.
  • node scripts/polish-agent-package-claude-max.mjs → grep-confirmed both ## Generating a dashboard and ## Configure before you commit + the one-liner rule landed in system_prompt.
  • Order-independence proven: update→polish and polish→update both converge to the complete 2.19.0 pack.
  • @pcc/mcp-server tsc --noEmit clean on Spark (exit 0); gateway routes tsc-clean on Spark.

Scope

Distribution-prep only — no deploy (owner-gated normal GHCR/Railway promotion). No CI/CD or Dockerfile touched. No new deps.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vhf19Lf2GJYw3wBa5kyuNP

LamaSu and others added 17 commits July 16, 2026 08:59
…rols)

projected DashboardManifest -> a closed, PCC-owned IR (strict subset of Atelier's
UiTree). PCC picks every node type from a frozen catalog + rebuilds every prop
from a closed per-kind grammar (never spreads a projected nested object).
Emits NO actions/capability (inert != safe: deceptive pixels); effecting kinds
render neutral (form->summary, approval->notice, chain->plan, actions->read-only
badges); trust-bearing content (receipt/approval) is a server-schema'd bind, not
manifest prose; fails closed on unknown/proto/limits. + validateIr structural
oracle. Standalone conformance: 22/22 (node --experimental-strip-types).

Round 2 of the sol-audited build loop. Design: ai/research/pcc-genui-b-closed-ir-design.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(sol R2 folded)

Round 3. Folds sol's Round-2 audit: strict REJECTION (no strip/clamp/truncate) +
exact-key validation at every level; per-kind binding policy (route-family +
selector/query grammar; receipt/approval require a server response schema);
recursive __proto__/constructor rejection anywhere; ALL manifest prose marked
untrusted (title/section/labels/actions); independent whole-tree validator with
per-type prop/bind schemas + parent/child + untrusted provenance; metric omits
format (PCC pre-formats); full node budget. Conformance 37/37.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-3 verdict was FAIL-not-ready. All six blocking findings closed:
1. onlyKeys/strict-rejection at EVERY level (actions, field-defs, submit/
   approve/deny/composeRef/execute); credential field names rejected.
2. section shape fixed to real projection {heading,windows} (was {title}).
3. BIND_POLICY end-anchored per-kind route allowlist matching real routes
   (metric excludes stripe/credits+sessions; list excludes evidence+
   marketplace-orders; capability excludes types/templates/search; receipt
   only settlement/evidence routes; +jobs/:id/status, settlement/:id).
4. PATH_GRAMMAR rejects single-dot segment + {}; SELECT_GRAMMAR rejects
   proto segments; query values require Number.isFinite; sse per-kind allowlist.
5. deepClean via isPlain prototype check (Object.prototype|null only) +
   recursive own-key proto rejection — catches nonstandard prototypes.
6. validateIr mirrors adapter: per-type prop schemas, tone enum, required
   props, nN id format, noBind types, parent/child grammar.
approval-notice emits NO bind (static notice). 48/48 conformance.

AGENT_NAME: implementer-genui-b
…able

sol R4 = FAIL. Findings 2,4 were already CLOSED; 1,3,5,6 + new gaps now closed
against the GROUND-TRUTH route table (sol was right — CLAUDE.md's API doc drifted;
verified every route in src/routes/*.ts):

1 (strict rejection): metric `format` now REJECTED not ignored (§6.2: PCC pre-
  formats, IR omits format); metric `select` is top-level + REQUIRED (was silently
  dropped → whole-object leak); form.submit/chain.execute/approve/deny/actions now
  pass a closed isOpDescriptor grammar (reject, not accept-and-strip); credential
  field block normalized (strip _/-, lowercase) → catches api_key/private_key/
  authorization/bearer/userPassword; schema.type/required type-checked.
3 (bind policy = real routes): /api/fiat-ramp/wallet/balance was a DEAD route →
  replaced with real /api/fiat-ramp/cdp/wallet/:address/balance; receipt = real
  /api/settlement/:jobId + /api/evidence/:jobId (dropped invented jobs/:id/settlement);
  RESERVED_EXACT blocks collection-collisions (kernels/marketplace, settlement/
  status+epochs, capabilities/types+templates+search); pool/marketplace dropped.
5 (own-key rejection): onlyKeys + deepClean now iterate Reflect.ownKeys → reject
  symbol + non-enumerable own keys, not just Object.keys.
6 (validator parity): validateIr now calls the SAME bindMatchesPolicy as the adapter
  (route allowlist, select/query/pollMs/sse grammar, sse/path correlation, schema
  only on receipt); enforces card/grid/plan kind enums, required binds, per-type
  child grammar, childless leaves, own-property required-prop check, H1 title.
New gaps: metric select required; REST/SSE job identity CORRELATED (run reads job A
  ⇒ streams job A); frozen catalog trimmed to the exact 14 emitted types (progress/
  divider removed); conformance now uses only real routes.

Verified: 82/82 deps-free conformance + a REAL projectDashboardForMcpApp→adapter→
validateIr integration (dashboard-ir.integration.test.ts, vitest/CI) — also run
locally via extracted-projection harness, 12/12 (schema smuggle survives cloneData
and the adapter is the backstop; semantic-escalation + dead-route + sse-mismatch
rejected end-to-end).

AGENT_NAME: implementer-genui-b
…ly sinks

The read-only paint layer for the closed IR (dashboard-ir.ts). Security contract
mirrors the adapter and treats the HOST as untrusted (§6.3):
- bootIrView runs the injected validateIr IN-BROWSER before painting; an invalid or
  tampered (off-catalog) doc paints ONE inert notice and nothing else.
- FROZEN painter dispatch: Object.freeze'd map of exactly the 14 catalog types;
  node.type can never name a painter/handler/tag. Unknown type → inert.
- TEXT-ONLY sinks: every string reaches the DOM via textContent; the module never
  references innerHTML/insertAdjacentHTML/outerHTML (grep-verified — only in the
  header comment describing their absence). Untrusted prose gets a marker class.
- Schema-validated dynamic rows: bindListRows/bindScalar read ONLY the doc's declared
  own-property selectors (proto segments guarded), drop malformed rows, and never
  surface non-selector fields (a row's stray `secret` is never rendered).
- NO EFFECTS: no fetch, no host tools/call, no __PCC_HOST_BRIDGE__/OPERATIONS (item 8
  wires GET-only data binding). Self-contained for .toString() inlining.

Verified: 20/20 deps-free conformance (node --experimental-strip-types).

AGENT_NAME: implementer-genui-b
sol R5 = FAIL (findings 1,3,5,6 PARTIAL + 3 new). Closed:

1 (strict rejection): isOpDescriptor `confirm` is the real enum "inline"|"approval"
  (spec/ui-artifact.ts), NOT boolean — the R5 boolean check REJECTED real projected
  actions (breaking valid dashboards) while accepting malformed ones. Verified a real
  confirm:"inline" action now survives projection→adapter.
3 (bind policy): RESERVED_EXACT completed from a full src/routes sweep — closes the
  semantic leaks sol found: /api/evidence/lit-status (Lit service status rendered as a
  RECEIPT), settlement submit/flush/release/status/epochs, jobs/submit, evidence/archive.
5 (own-key): onlyKeys now rejects NON-ENUMERABLE allowed-name own keys (hidden
  data-shaped props); deepClean's array branch rejects symbol / non-index own keys on
  arrays (sections/windows/children); nested objects reject non-enumerable keys too.
6 (validator parity): validateIr now mirrors the adapter's CARDINALITY — root ≤24
  sections, section ≤32 windows + heading H2-only-at-index-0, grid 1..64 badges,
  form-summary ≤64, list limit integer 1..200, prose length caps (400/2000), and the
  approval-notice text must EQUAL the one fixed PCC sentence (not manifest prose).
New: poll-amplification capped — pollMs floor raised 250→5000ms + an aggregate
  bound-window budget (≤64 binds/dashboard), enforced in BOTH adapter and validator.
  Integration test hardened per sol: query/route rejections now carry `select` so
  they're ATTRIBUTED, + a real confirm:"inline" action + lit-status/settlement-status
  collision cases.

Note (unchanged, documented): the real projectDashboardForMcpApp STRIPS unknown
top-level/window fields (pre-existing launched behavior) while the adapter REJECTS —
two-layer defense; no unknown field reaches render either way. Upstream reject-not-strip
is a projection-hardening follow-up, out of B-adapter scope.

Verified: 104/104 deps-free conformance + 20/20 renderer + 8/8 real-projection chain
(incl. real confirm:"inline" action). vitest integration updated for CI.

AGENT_NAME: implementer-genui-b
…ense (sol R6)

sol R6 = FAIL narrowly: 1/5/6/poll/integration all CLOSED; one blocking route
collision + two test/availability nits. Root cause of the recurring collisions: a
hand-maintained reserved-word BLOCKLIST is never provably complete — my "full sweep"
kept missing route files (graph-search.ts, paid-job-flow.ts, evidence-search.ts), so
/api/capabilities/graph-stats (real GET → graph statistics) matched /api/capabilities/:seg
and would render AS A CAPABILITY.

Fix — reject the whole class by CONSTRUCTION, not enumeration:
- ID_SEG grammar + route() compiler: a detail-template `:` segment must NOT be a bare
  lowercase-alpha(-hyphen) word. PCC ids carry a digit/underscore/uppercase/0x (uuids,
  cap-1, kernel_x, 0x…); the colliding collection/status/verb names (types, status,
  graph-stats, lit-status, submit, submit-from-discovery, …) are pure alpha — rejected
  by the negative-lookahead, robust to NEW sibling routes. Verified: graph-stats/printer
  rejected, cap-1/kernel_printshop_alpha (underscore) accepted, end-to-end too.
- RESERVED_EXACT kept as a documented secondary denylist (+ graph-stats/graph-search/
  submit-from-discovery/evidence-embed/evidence-search) for any future digit-bearing word
  the grammar can't catch (none currently).
- deepClean now BUDGETED (LIM.cleanNodes=50k shared counter) — a hostile wide/shallow
  object fails fast instead of forcing full traversal before rejection (browser-side
  validateIr availability; sol R6 additional-3).
- Test fixes (sol R6 additional-2): the 66-bind aggregate test now spans 3 sections so
  the aggregate cap fires before the per-section window cap masked it; added a direct
  independent validateIr aggregate test.

Verified: 111/111 deps-free conformance + 20/20 renderer + real-projection chain
(graph-stats + pure-alpha capId rejected end-to-end). vitest updated for CI.

AGENT_NAME: implementer-genui-b
…ardown

The one new browser capability Phase B adds, built to sol's R6 pre-wiring checklist
and kept small + testable ahead of the live-file wiring:
- bindUrl: every URL is origin+bind.path(+query) ONLY; refuses //host, scheme, ..,
  whitespace, embedded query/fragment, non-/api — a bind can't escape the PCC origin.
- GET-only + no redirects: the injected getJson uses hardcoded GET/redirect:error;
  the binder consumes a body ONLY on status==200 && !redirected && !bytesOver.
- SSE XOR poll (channelFor): exactly one live channel per node, never both.
- Bounded: clampPoll floors cadence at 5s; a 30-min session timer auto-stops the node.
- Tearable: stop() clears the poll timer, closes the SSE, and latches so no late
  callback paints after unmount.
- No auth in URL: only bind.query reaches the URL; auth is the transport's business.
Dependency-injected (getJson/openSse/timers) → testable now, self-contained for
.toString() inlining next to the renderer.

Verified: 27/27 deps-free conformance; grep-clean of non-GET methods / auth material /
DOM sinks.

AGENT_NAME: implementer-genui-b
…R7 follow-up)

sol R7 = PASS for wiring. Folded its two non-blocking findings at the module level:

1 (id-grammar not future-complete): added dashboard-ir.route-inventory.conformance.ts
  — scans all 117 real src/routes/*.ts files, auto-inventories every static
  single-segment sibling of each id-taking detail template (capabilities/settlement/
  evidence/jobs/kernels/escrow), and proves the adapter's bind gate REJECTS all 27
  found (+ 4 id-shaped positive controls accepted). If a future colliding sibling
  (e.g. a digit-bearing one the grammar can't catch) is ever added, THIS test fails —
  the durable guard sol asked for, instead of trusting a hand-maintained blocklist.

Binder (sol R7 checklist additions): failure backoff (consecutive non-clean responses
  back off exponentially, capped; a clean 200 resets) with ONE in-flight request per
  bind — a timer never overlaps its request. Tightened the getJson/openSse dep
  CONTRACTS to require credentials:"omit" + fixed headers, incremental byte enforcement
  + JSON content-type, and fetch-streamed SSE (not native EventSource, which follows
  redirects) with event size/rate/reconnect/lifetime limits.

Deferred to the item-8 wiring (not module-level): the outer serialized-size cap before
validateIr (sol finding 2), global bind concurrency cap, and pause-while-hidden.

Verified: route-inventory 31 assertions (27 siblings + 4 controls) + binder 29/29.

AGENT_NAME: implementer-genui-b
… modules

The modules imported each other with .ts specifiers (for the deps-free
--experimental-strip-types harnesses), but the gateway is NodeNext + `tsc`-emit
(existing code imports "./operation-ids.js"). tsc rejects .ts import extensions
without allowImportingTsExtensions (which needs noEmit), so the branch would have
failed the gateway build + CI, and the incoming esbuild bundle needs the real
convention too. Converted all 7 local imports (adapter/renderer/binder + 3
conformance harnesses) to .js — now correct for tsc, vitest, and esbuild (all
resolve .js→.ts). Local strip-types verification retained via a small dev-only
resolve loader (scratchpad, not shipped). No logic change.

Verified via loader: adapter 111 + renderer 20 + binder 29 + route-inventory 31.

AGENT_NAME: implementer-genui-b
…byte-checked)

Per the bundle-from-TS decision: the browser security kit is GENERATED from the
audited modules, never hand-written — the reviewed bytes are the shipped bytes.

- src/mcp/dashboard-ir-browser-entry.ts — thin browser composition root. Imports the
  audited adapter/validator/renderer/binder UNCHANGED; adds only browser glue: a
  minimal READ-ONLY MCP Apps lifecycle (init handshake → tool-result → render, NO
  bridge), the outer 256KB manifest size cap, real transports (GET fetch with
  credentials:"omit" + redirect:"error" + incremental 512KB cap + JSON content-type;
  fetch-streamed SSE, never EventSource), a global 6-way bind-concurrency cap,
  visibility pause/resume, and teardown. Exposes no host-call surface; contains no
  tools/call / __PCC_HOST_BRIDGE__ / __PCC_HOST_OPERATIONS__ / capability registration.
- scripts/build-dashboard-ir-kit.mjs — deterministic esbuild bundle (iife, es2022,
  unminified, no sourcemap) + a forbidden-identifier scan (bridge/tools-call/
  registerComponent). --check does an in-memory rebuild + byte-compare.
- apps/dashboard/public/ui-kit/v1/pcc-ir-kit.js — the COMMITTED generated bundle
  (45248 bytes, unminified). Verified byte-identical to a fresh esbuild@0.27.3 build;
  forbidden identifiers absent; security core (dashboardManifestToIr/validateIr/
  bootIrView/startBind/RESERVED_EXACT) present.
- package.json — build:ir-kit + check:ir-kit scripts; check wired into `test` so CI
  gates on byte-equivalence; esbuild pinned EXACT 0.27.3 (== lockfile) as a direct
  devDep for determinism.
- tsconfig.json — exclude the browser-only entry + the *.conformance.ts dev harnesses
  from the server tsc build.

Next: mcp-app-view.ts wiring (readPccIrKitSource + primeMcpAppAssets + B-mode ui://
resource, textContent-injected, no bridge) + Dockerfile asset gate + a behavioral
browser test on the committed bytes. Then sol review.

AGENT_NAME: implementer-genui-b
…item 8)

mcp-app-view.ts stays WIRING (not implementation): locate+cache pcc-ir-kit.js
(readPccIrKitSource), fail startup if absent (primeMcpAppAssets + _reset), inject via
a nonce boot script's <script>.textContent, and register the read-only B-mode resource
ui://pcc/dashboard/render-ir — served with the same CSP/nonce/strict-dynamic shell as
the A view, NO __PCC_HOST_BRIDGE__ / __PCC_HOST_OPERATIONS__ / tools-call. Registered
ALONGSIDE the A render path (additive, non-destructive); the tool-repoint that makes B
the live render is the audited promotion. Dockerfile asset gate now also asserts
pcc-ir-kit.js ships in the image.

Behavioral test (dashboard-ir-kit.browser.test.ts, vitest+jsdom on the COMMITTED bytes):
a fresh jsdom window per case drives the read-only lifecycle and proves — valid renders
as inert text (no <b>/script/iframe), invalid/oversized stays inert, non-parent source
ignored, renders once, NEVER emits tools/call — plus a static negative scan for the
forbidden bridge/write identifiers. jsdom pinned exact 24.1.3 (== lockfile). Verified
locally 9/9 against the committed bytes via an isolated jsdom run (the render-fail I hit
first was a Node-realm test artifact — a real postMessage structured-clones into the
view realm, which the test now simulates; isPlain correctly rejects a foreign-realm
object, no bundle change).

Confirmed: entry is debug-free and the committed bundle is byte-identical to a fresh
build of it (the check:ir-kit invariant).

AGENT_NAME: implementer-genui-b
… build)

sol R8 = NO-GO on the composition root (approach GO). All findings folded; security
CORE untouched (sol confirmed source-gate/render-once/no-write/slot-correlation correct).

HIGH:
- Lifecycle: ui/initialize (params appInfo{name,version} + appCapabilities) →
  ui/notifications/initialized → ui/notifications/tool-result; answers
  ui/resource-teardown with a JSON-RPC result (was the wrong non-ui methods, no teardown).
- Fixed PCC API origin: the kit binds against window.__PCC_IR_ORIGIN__, injected by the
  trusted server boot script from MCP_APP_API_BASE_URL (matches the CSP connect-src) —
  never location.origin/parent/manifest. No valid origin ⇒ static render, no binding.
- Abort/concurrency: per-render bind GENERATION AbortController — visibility-hide /
  teardown / pagehide abort in-flight GETs and drop queued waiters (visibility churn can
  no longer grow work unbounded); the concurrency gate's waiters are cancellation-aware
  and a woken waiter inherits the permit (no barge past 6).
MEDIUM:
- Bounded manifest size preflight (nodes/chars/fanout) BEFORE any serialize.
- Exact application/json essence check; every non-clean response (non-200/redirected/
  wrong-type/oversize) cancels the body (no 512KB-cap bypass).
- SSE transport REMOVED (run cards are unbound → the audited SSE policy was unreachable).
LOW:
- No AbortSignal.timeout dependency (manual controller + setTimeout).

Build determinism: byte-compare Buffers (not decoded strings), absWorkingDir pinned to
gatewayRoot, metafile 4-file input allowlist, root pnpm override esbuild pinned EXACT
0.27.3. Added tsconfig.browser.json + `typecheck:browser` (esbuild doesn't type-check the
entry) — which CAUGHT + fixed real pre-existing type errors in the audited dashboard-ir.ts
(strictStr max param inferred as literal 2000 from `as const`; validateIr bind cast). Both
are annotation-only (esbuild erases them) so the bundle is byte-identical; they unblock the
gateway tsc build.

Verified: browser typecheck clean; 111+20+29+31 conformance; 10/10 jsdom smoke on the
committed R9 bytes (ui/* lifecycle + teardown + origin + bounded preflight + render-once);
entry↔bundle byte-identical (46282 bytes).

AGENT_NAME: implementer-genui-b
…c preflight, build gates)

sol R9 = NO-GO (narrowing; R8 findings 2/5/6 closed). Remaining composition-root +
build-gate blockers folded; security core untouched.

Composition root (dashboard-ir-browser-entry.ts):
- Finding 1 (teardown not terminal): a terminal `disposed` latch, set BEFORE stop+ack on
  ui/resource-teardown and checked by the message / visibility / render paths — a
  post-teardown tool-result no longer renders, and a later visible visibilitychange no
  longer restarts binds. Verified in jsdom.
- Finding 2 (preflight unbounded for cyclic/shared graphs): tooLarge() now uses a WeakSet
  (skip visited), caps enqueued work (maxPending) BEFORE pushing, enforces per-container
  fanout via for-in + early-break (no Object.keys() materialization), and charges
  property-name chars. A self-referential 5000-pointer object now stays inert without
  hanging. Finding 4 boundedness objective closed.
- Finding 3 (makeSignal cleanup leak): removed; getJson now owns a per-request
  AbortController linked to the generation signal + a timeout, and clears the timer +
  removes the generation listener when the request SETTLES (no leaked timer/listener).

Build/test gates:
- pnpm-lock.yaml regenerated so the esbuild override is EXACT 0.27.3 (was ^0.27.0) —
  --frozen-lockfile in Docker/CI now matches package.json; jsdom@24.1.3 recorded.
- Dockerfile runs `check:ir-kit` in the build stage (byte-equivalence fails the image
  build on drift), matching the build-script comment.
- CI adds an explicit browser typecheck step (the generic `tsc --noEmit` excludes the
  browser entry).
- Tests: exported buildMcpAppIrDashboardHtml + a B-resource composition test (asserts the
  injected fixed origin, embedded kit, CSP, and ABSENCE of the A-view bridge/tools-call);
  added terminal-teardown (before-result + visibility-churn) and cyclic-manifest cases.

Verified: browser typecheck clean; entry↔bundle byte-identical (47203B); 111+ conformance;
R10 jsdom smoke (cyclic inert/no-hang, teardown terminal).

AGENT_NAME: implementer-genui-b
…locking)

sol R10 = GO for the additive read-only B resource. Its 3 non-blocking test-coverage
notes (tests that would pass even if the fix regressed) are addressed so the suite
actually exercises the R10 fixes:
- teardown-terminal now COUNTS fetch starts (was "does not throw") and asserts the
  count is frozen after a post-teardown visible visibilitychange — a real restart
  would now fail the test.
- added init-result-AFTER-teardown → never becomes ready / renders.
- the cyclic case is now a SMALL self-cycle (exercises the WeakSet, not the fanout
  cap) → inert without hang; plus a bounded shared-DAG (20 refs to one section) that
  RENDERS — proving the WeakSet neither explodes nor false-rejects legit sharing.

Test-only; bundle unchanged (byte-identical). Verified against the committed 47203B
bytes via jsdom: 5/5 (initial fetch, frozen after teardown, init-after-teardown
ignored, self-cycle inert, shared-DAG renders).

AGENT_NAME: implementer-genui-b
…) + robust shared-DAG test

Spark pre-land verification (the first full-workspace run) caught two issues my
deps-free local checks missed:

1. The committed bundle was NON-CANONICAL. My local isolated build env lacked the real
   gateway tsconfig, so esbuild didn't see `strict:true`/`alwaysStrict` and omitted the
   leading `"use strict";` — 47203B vs the canonical 47217B that the full workspace
   produces. Confirmed tsconfig-driven, NOT platform (local build with the real tsconfig
   now byte-matches). Committed the canonical 47217B bundle; Spark `check:ir-kit` passes
   byte-identical. (My local build env is fixed to include the tsconfig so future rebuilds
   are canonical.) The determinism gate is now real: committed == pnpm-locked esbuild build.

2. The shared-DAG test asserted a specific RENDER outcome, which is environment-dependent;
   the WeakSet's actual guarantee is TERMINATION (bounded, no hang). Reworded to assert the
   preflight processes the shared-DAG to a bounded result (not "waiting") — the real property.

Spark results confirming these were the only issues: install (frozen-lockfile) OK; the
integration test (real projection) 15/15; browser test 14/15 (only the shared-DAG assertion,
now fixed); typecheck:browser passes once @pcc/spec is built (the earlier failure was a
build-order artifact of a direct-tsc bypass, handled by CI's turbo build order).

AGENT_NAME: implementer-genui-b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant