open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcr1/conformance/ui-output-vectors.json
Transaction Science 42216acb1f jouleclaw: wave-48 — UI-output ladder + per-rung Oracles + JCR-1 ui-output/1
Deep-dive on Mira Murati's Interaction Models paper confirmed the
2026 streaming primitive set (276B MoE / 12B active TML-Interaction-
Small, continuous interleaving of 200ms input + 200ms output,
multi-stream parallel audio/video/text). Cross-reference: the
generative-UI side converged on the same window — MCP Apps (Jan 26
2026), AG-UI, Google A2UI, Open-JSON-UI all settled on three rungs
(Static / Declarative / Generative) with iframe sandbox for the
generative rung, matching Ruben Casses's Postman keynote ladder.

The two axes — interaction tempo (Mira) and UI-generation rung
(Ruben) — are orthogonal. Wave-47 typed up the first axis. Wave-48
types the second + composes them.

Four deliverables:

1. jouleclaw-ui-output (new crate) — pure types.
   - UiOutput { Static { component_id, props } | Declarative
     { schema_id, descriptor } | Generative { language, source,
     capabilities } }. Each rung carries the engineering invariant
     its trust level requires.
   - SandboxCapability + CapabilityPolicy { Empty | Allow(set) }
     with check_all() returning first denied — the JouleClaw
     doctrine demands default-empty allow-lists.
   - ComponentRegistry for Static-rung validation (registered
     allow-list, idempotent on id).
   - DEFAULT_GENERATIVE_HTML_BYTES = 32 KiB, _REACT_BYTES = 64 KiB
     + default_generative_budget() lookup.
   - UI_OUTPUT_MIME = "application/x.jouleclaw-ui-output+json" —
     stable MIME so a jouleclaw-stream::Tick can carry a UiOutput
     in its bytes WITHOUT extending the tick schema (and breaking
     wave-47's stream-window/1 conformance vectors).
   - Canonical JSON (sorted keys) + content_hash + payload_bytes
     for receipt content-addressing and rolling-conservation
     accounting.
   - 11 tests.

2. jouleclaw-ui-oracle (new crate) — per-rung Oracle backends.
   - StaticUiOracle — component_id ∈ ComponentRegistry.
   - DeclarativeUiOracle — schema_id matches + descriptor.kind ∈
     allowed_kinds. (Schema validation of the full descriptor is
     the renderer's job; this gate refuses unrecognised kinds
     before the renderer is reached.)
   - GenerativeUiOracle — CapabilityPolicy.check_all + per-
     language byte budget + caller-configurable deny-substring
     list (default empty; callers seed `"eval("`,
     `"document.write("`, etc. per their threat model).
   - All three implement the per-request jouleclaw-oracle::Oracle
     trait so they compose via CompositeOracle alongside the
     FuzzOracle and the future Lean Oracle, AND lift into the
     streaming cascade via jouleclaw-stream-oracle::PerTickAdapter.
   - 13 tests covering each rung's pass/fail paths + wrong-rung
     rejection + composition.

3. jouleclaw-jcr1 — ui-output/1 schema.
   - UiOutputReceipt { jc_receipt, id, closed_at, steward, rung,
     content_hash, byte_length, granted_capabilities, verdict,
     stream_id, window_id }. Optional stream_id + window_id
     correlate the UI receipt with a stream-window/1 receipt for
     full-session reconstruction.
   - UiRungWire { static | declarative | generative };
     UiVerdictWire { Pass { oracle_id } | Fail { subkind, message } }.
   - 2 conformance vectors (Static Pass with stream correlation;
     Generative Fail standalone) with embedded receipt_hash the
     Rust verifier asserts byte-equal at test time.
   - 3 ui_output tests on top of the existing jcr1 surface.

4. Demo binary — examples/wave48_ui_streaming.rs in
   jouleclaw-stack. Four UI outputs ship through the gates: one
   per rung passes (Static / Declarative / Generative-with-
   clipboard), plus one Generative-with-net.fetch that's correctly
   denied by the capability policy. Each render mints a ui-output/1
   receipt correlated to a streaming-window id.

Composition summary across waves 46-48:

  per-request:        Candidate → CompositeOracle (Fuzz | UiRung*)
                                  → Verdict → JCR-1 verified-code/1
                                            OR JCR-1 ui-output/1

  streaming-per-win:  StreamWindow → CompositeStreamOracle (BargeIn
                                      | PerTickAdapter<UiRungOracle>)
                                  → WindowVerdict → JCR-1 stream-window/1
                                  + RollingConservationLedger
                                  + per-output JCR-1 ui-output/1
                                    (correlated by stream_id+window_id)

The triangle (conservation + cone + oracle + elicitor) types every
slot up; wave-46 / 47 / 48 fill backend slots without rewriting the
triangle itself. The MCP-Apps / AG-UI / A2UI ecosystem can target
jouleclaw-ui-output as the substrate-grade common type for what
an agent emits to the front-end, with receipts proving which rung
shipped, which capabilities were granted, and which gate signed
off.

Workspace check clean.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-05 04:39:03 -04:00

50 lines
2.2 KiB
JSON

{
"spec": "JCR-1 ui-output v1",
"schema_id": "ui-output/1",
"canonical_encoding": "JSON with sorted object keys (recursively); UTF-8; no whitespace; no float coercion. receipt_hash is BLAKE3 hex of the canonical bytes.",
"note": "Two vectors covering the two extremes of the UI-output ladder under JCR-1: a Static-rung Pass that ships inside a streaming window (correlated by stream_id + window_id), and a Generative-rung Fail (capability denied) that ships outside a streaming session (empty stream correlation). Implementers in any language MUST round-trip every receipt under canonical encoding and assert byte-equal receipt_hash against the embedded value.",
"vector_count": 2,
"vectors": [
{
"n": 1,
"description": "Static Pass — registered component rendered inside stream-demo window 3.",
"receipt_hash": "07a1da03f2c1e4ec881c9b00a5e1efca36c562cc665bc792ff8a38d68c561359",
"receipt": {
"jc_receipt": "ui-output/1",
"id": "ui-001",
"closed_at": "2026-06-05T00:00:00Z",
"steward": "did:keri:EJ9testStewardAID000000000000000",
"rung": "static",
"content_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"byte_length": 128,
"granted_capabilities": [],
"verdict": {
"kind": "pass",
"oracle_id": "static-ui-gate"
},
"stream_id": "stream-demo",
"window_id": 3
}
},
{
"n": 2,
"description": "Generative Fail — requested capability `net.fetch` not in host policy; standalone (no stream correlation).",
"receipt_hash": "2e9b216eecee0253a1834eeaebdaea5354fe638db901ff6337cf0bb3e35b3aa5",
"receipt": {
"jc_receipt": "ui-output/1",
"id": "ui-002",
"closed_at": "2026-06-05T00:00:01Z",
"steward": "did:keri:EJ9testStewardAID000000000000000",
"rung": "generative",
"content_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"byte_length": 4096,
"granted_capabilities": [],
"verdict": {
"kind": "fail",
"subkind": "disallowed-capability",
"message": "requested `net.fetch` not in capability policy"
}
}
}
]
}