Every sealed interaction now names the principal that fired it. The actor is a required field on Interaction, a required argument to run_interaction, and is bound into the signed canonical core — so an interaction can no longer be attributed to a different principal without breaking the chain. Human and agent are traced identically (a did:key either way); the standard draws no distinction. An unauthenticated host attributes fires to its own session principal — joule-ui-serve defaults this to the host's own did:key (derived from its signing key) and exposes with_session_actor for deployments that authenticate the caller. The canonical-core profile tag is bumped joule-ui/interaction/1 -> /2, since the actor is now part of the signed preimage; the two cores are distinct bytes and never collide. SPEC §7.1 + C2/C3 updated, conformance v1 vectors regenerated, examples and the conformance harness pass an actor. Co-Authored-By: Claude Opus 4.8 <[email protected]>
163 lines
8.4 KiB
Markdown
163 lines
8.4 KiB
Markdown
# joule-ui — Specification
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Standard** | joule-ui |
|
||
| **Status** | Draft for Review |
|
||
| **Version** | 0.2.0 |
|
||
| **Licence** | Specification text CC-BY-4.0 ([`LICENSE-SPEC`](LICENSE-SPEC)); reference code Apache-2.0 ([`LICENSE`](LICENSE)) |
|
||
| **Reference** | [`joule-ui-rs/`](joule-ui-rs/) |
|
||
|
||
## §1 What joule-ui is
|
||
|
||
joule-ui is a typed protocol for **the moment a computation has to become
|
||
something a human sees and acts on** — and for making that moment *accountable*.
|
||
It has two layers, and the second is the one no other UI protocol carries:
|
||
|
||
1. **The surface (§2–§4).** A host publishes a **registry** of widgets it can
|
||
render; an agent or program emits a **typed widget tree** against it. The
|
||
host renders. No HTML over the wire, no code emission, no agent holding the
|
||
renderer. This is the shape the field converged on (A2UI, MCP Apps SEP-1865,
|
||
CopilotKit's declarative middle); joule-ui pins it as a protocol.
|
||
|
||
2. **The interaction (§5–§7).** When a human fires an action on that surface,
|
||
the invocation is an **accountable event**: authorized against a capability,
|
||
metered in joules, and sealed into a tamper-evident receipt. A joule-ui
|
||
session is therefore not a stream of opaque callbacks — it is a hash-chained
|
||
ledger of *what the user did, what it computed, and what it cost*, verifiable
|
||
by anyone holding the host's public key.
|
||
|
||
The surface layer makes joule-ui a peer of the declarative-UI protocols. The
|
||
interaction layer makes it the only one you can hand to an auditor.
|
||
|
||
## §2 The widget tree
|
||
|
||
A **Widget** is `(name, props, children)`, recursive. `name` selects a widget
|
||
in the host's registry; `props` is a map of typed values; `children` is an
|
||
ordered list of Widgets. The wire form is plain JSON.
|
||
|
||
A **PropValue** is a closed set: `Null`, `Bool`, `Number`, `Text`, `List`,
|
||
`Object`, and **`Action`** — an opaque action id (plus optional pure-data
|
||
payload) that the host binds to a handler at render time. There are no
|
||
closures, no expressions, and no code on the wire. An interaction (§5) is the
|
||
firing of an `Action`.
|
||
|
||
## §3 The registry
|
||
|
||
A **Registry** is the host's published catalog: for each widget name, a
|
||
**WidgetSchema** declaring its prop slots (name → type, required/optional), the
|
||
children it accepts (`None` / `Any` / a named allow-list), and whether
|
||
unknown props are permitted. The registry is the host's contract; the renderer
|
||
is the host's product surface and is out of scope for this standard.
|
||
|
||
## §4 Validation
|
||
|
||
`validate(widget, registry)` checks a tree against the registry and returns
|
||
**every** error, not just the first: unknown widget, unknown/missing/mistyped
|
||
prop, disallowed child. A conforming validator MUST be exhaustive and MUST be
|
||
pure (no IO). A surface that validates is renderable by any conforming host of
|
||
that registry; validation is the well-formedness attestation the surface layer
|
||
provides.
|
||
|
||
## §5 Energy and provenance
|
||
|
||
Every interaction carries a two-part energy figure in integer **microjoules**:
|
||
`measured_uj` (present only from a hardware interface) and `estimated_uj`
|
||
(always present), tagged with a **Provenance** — `HwShunt` (a hardware energy
|
||
reading), `ModelBased` (a calibrated apportionment), or `Estimator` (a coarse
|
||
constant). This is the family's shared energy model; a figure is reported with
|
||
the honesty of how it was obtained, never promoted.
|
||
|
||
Measurement itself is a **recognized edge**, not part of this standard: a
|
||
`Meter` is any source of a cumulative microjoule counter, and the sibling
|
||
**Sandbox** standard's `PowerMeter` (real RAPL/IOReport, and CPU-time
|
||
apportionment) is the reference one. joule-ui defines what an interaction
|
||
records; the host wires the meter it trusts.
|
||
|
||
## §6 The interaction lifecycle
|
||
|
||
`run_interaction` is the protocol in one operation, in three phases:
|
||
|
||
1. **Authorize before running.** The action is checked against a capability
|
||
*before* its handler executes. A denied action's handler **MUST NOT run**.
|
||
The capability check is a recognized edge: an **Authorizer** is any
|
||
two-phase `(authorize, charge)` gate, and the sibling **JCP** standard's
|
||
runtime — gating on a signed, joule-budgeted grant — is the reference one.
|
||
2. **Meter the handler.** The handler runs once; the meter is sampled before
|
||
and after; the difference is the interaction's energy. With no hardware
|
||
meter the figure is honestly `Estimator`.
|
||
3. **Charge the measured cost.** The measured joules are debited from the
|
||
capability's remaining budget — so the budget that stops a runaway UI is
|
||
energy *actually spent*, settled after the fact, not a token estimate.
|
||
|
||
The result is an **Interaction**: the surface's content address (what the user
|
||
was looking at), the action id, the content addresses of the inputs and the
|
||
outputs (the genesis sentinel when denied), the verdict, the energy, the budget
|
||
left, and the grant id. Inputs and outputs are content-addressed, not carried,
|
||
so the receipt commits to *what was supplied and produced* without embedding it.
|
||
|
||
## §7 The sealed, hash-chained interaction log
|
||
|
||
An **InteractionLog** seals each interaction as the next link of a chain. The
|
||
**canonical core** (§7.1) is signed with the host's Ed25519 key; each link's
|
||
`prev` is the content address of the previous link's core, and `seq` increments
|
||
from zero. **Allows and denies are both chained** — a denial excised from the
|
||
record is exactly the audit a malicious host would drop, so excising,
|
||
reordering, or splicing any interaction MUST break the chain at that point.
|
||
`verify_chain` checks every signature, the sequence, and every `prev` link, and
|
||
reports the first break.
|
||
|
||
### §7.1 The canonical core
|
||
|
||
The signed bytes are a fixed-order, length-prefixed concatenation: the profile
|
||
tag `joule-ui/interaction/2`, then surface, action, **actor**, inputs, outputs,
|
||
verdict (and deny reason, if any), provenance, `estimated_uj`, `measured_uj` (if
|
||
present), `budget_remaining_uj` (if present), `grant_id` (if present), `seq`,
|
||
and `prev`. Each field is preceded by its little-endian `u64` byte length.
|
||
|
||
The **actor** is the principal that fired the interaction — the `did:key:z…` of
|
||
a human or an agent (the standard makes no distinction; both are traced the
|
||
same). It is **mandatory**: every sealed interaction names who fired it, and
|
||
that name is bound into the signed core, so an interaction cannot be attributed
|
||
to a different principal without breaking the chain. A host with no per-client
|
||
authentication attributes fires to its own session principal (by convention its
|
||
own `did:key`). The profile tag is `…/interaction/2` precisely because the actor
|
||
is now part of the preimage — a `…/1` core (no actor) and a `…/2` core are
|
||
distinct signed bytes and never collide.
|
||
Deterministic and dependency-light; the BLAKE3 of this core is the link's
|
||
content address. (A host MAY additionally express a sealed interaction as a
|
||
JCR-1 / in-toto+DSSE envelope for cross-family verification — a recognized
|
||
edge, not required.)
|
||
|
||
## §8 Conformance
|
||
|
||
- **C1 — surface validation.** Given a registry and a widget tree, reproduce
|
||
the exhaustive error list (empty for a valid tree).
|
||
- **C2 — interaction metering.** Given a surface, action, actor, inputs, a stub
|
||
meter, and a handler, reproduce the Interaction: verdict, energy, content
|
||
addresses, and the sealed actor.
|
||
- **C3 — authorization.** A denied action produces a deny Interaction whose
|
||
outputs are the genesis sentinel and whose handler did not run; the actor is
|
||
still named on the denial.
|
||
- **C4 — chain integrity.** Seal a sequence; `verify_chain` accepts it and
|
||
rejects any excision, reorder, or field mutation at the first broken link.
|
||
|
||
## §9 Recognized edges
|
||
|
||
joule-ui is self-contained at its core and composes outward at named edges:
|
||
the **Sandbox** standard supplies the hardware `Meter`; **JCP** supplies the
|
||
budgeted-grant `Authorizer`; **JouleContract** / in-toto+DSSE supply an optional
|
||
cross-family receipt envelope; and any transport (MCP, REST, SSE, gRPC, Web
|
||
MoQ) carries the validated surface and the interaction stream. None are required
|
||
to use the protocol; each is the reference answer where a host needs that edge.
|
||
|
||
## §10 Non-goals
|
||
|
||
A renderer (the host's), a transport (the consumer's), a component library
|
||
(product surface), and code emission (an agent emits a typed tree against the
|
||
registry, never a renderer). joule-ui pins the protocol and the receipt; the
|
||
fast-moving surfaces stay the consumer's.
|
||
|
||
---
|
||
*joule-ui v0.2.0 — the surface layer (v0.1) plus the metered, sealed
|
||
interaction layer that makes a UI accountable by construction.*
|