/** * A JSON object — the object branch of {@link JsonValue}. * Allows `undefined` values because TypeScript optional properties (`?:`) * produce `T | undefined`, and JSON objects can have missing keys. * * Used as the default generic parameter throughout the protocol where a * JSON-serializable object shape is expected (e.g., props, payloads, context). * Typed interfaces with optional properties satisfy `JsonObject` because * missing keys are `undefined` at runtime, which `JSON.stringify` omits. */ export interface JsonObject { [key: string]: JsonValue | undefined; } /** * Recursive type for any JSON-serializable value. * Use instead of `unknown` when the value MUST be JSON-safe * (no functions, symbols, bigint, etc.). * * Used as the default for fields that carry arbitrary JSON data * (e.g., error details, schema defaults, example values). * Prefer `JsonObject` when the value is known to be an object, * and `JsonValue` when it could be any JSON leaf or structure. */ export type JsonValue = string | number | boolean | null | JsonValue[] | JsonObject; /** * JSON Schema subset for defining data shapes in ggui contract. * Covers the types that map cleanly to TypeScript: primitives, objects, arrays, enums. * * Extends {@link JsonObject} so it can be used anywhere a JSON-serializable * object is expected (e.g., stored server-side, sent over WebSocket). * Fields like `default`, `example`, and `const` are typed as {@link JsonValue} * to accept any JSON-safe value. */ /** The JSON Schema primitive type vocabulary (draft-07). */ export type JsonSchemaTypeName = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null'; export interface JsonSchema extends JsonObject { /** * JSON Schema type. Optional when using `oneOf`/`anyOf` unions. * Draft-07 also admits a type ARRAY (`['string','null']`) — the * canonical nullability form the enforced-props-schema emission * (`buildEnforcedPropsSchema`) rewrites OpenAPI `nullable: true` * into, and what the validating engine's `nullable` widening * enforces. Additive widening of the authoring grammar (a type a * validator always accepted, now expressible in the TS type). */ type?: JsonSchemaTypeName | JsonSchemaTypeName[]; description?: string; /** Allowed values (enum constraint) */ enum?: JsonValue[]; /** Default value */ default?: JsonValue; /** Example value (for documentation / sample rendering) */ example?: JsonValue; /** For type: 'array' — schema of each array element */ items?: JsonSchema; /** For type: 'object' — property definitions */ properties?: Record; /** For type: 'object' — which properties are required */ required?: string[]; /** For type: 'object' — schema for additional properties beyond `properties` */ additionalProperties?: JsonSchema | boolean; /** For type: 'string' — format hint (e.g., 'date', 'email', 'uri') */ format?: string; /** For type: 'number' / 'integer' — minimum value */ minimum?: number; /** For type: 'number' / 'integer' — maximum value */ maximum?: number; /** Union: exactly one of these schemas */ oneOf?: JsonSchema[]; /** Union: any of these schemas */ anyOf?: JsonSchema[]; /** Constant literal value */ const?: JsonValue; /** OpenAPI 3.0 nullable shorthand */ nullable?: boolean; } /** * Per-prop metadata in a PropsSpec. * The `default` and `example` fields are {@link JsonValue} to accept any JSON-safe value * (string, number, boolean, null, array, or object). */ export interface PropEntry { /** Human-readable description of this prop */ description?: string; /** JSON Schema for this prop's type */ schema: JsonSchema; /** Whether this prop is required (component must accept it) */ required?: boolean; /** Default value if not provided. Typed as {@link JsonValue} (any JSON-safe value). */ default?: JsonValue; /** Example value (used for preview rendering). Typed as {@link JsonValue}. */ example?: JsonValue; /** * Which MCP tool produces this prop's data, if any. * Data-lineage metadata. When set, the agent must have this tool available * (or `required: false` on this prop) for the contract to be satisfiable. * When absent, the agent populates the prop by its own means (memory, * reasoning, search, etc.). The blueprint matcher aggregates these * for indexed primary-data-tool candidate lookups. */ sourceTool?: string; } /** * Props contract — defines the prop interface a generated component MUST implement. * * Shape: a wrapper `{description?, properties}` over the per-prop map. * * NOT flat like {@link ActionSpec} / {@link StreamSpec}, which dropped * their `{description, actions}` / `{description, channels}` wrappers * because their inner key name duplicated the parent * (`actionSpec.actions.createTask`). `PropsSpec.properties` is different: * `properties` is the JSON Schema field name for the per-property bag on * an object schema, so the wrapper matches a convention an external * implementer already knows from reading JsonSchema itself. Flattening * would also cost the top-level `description`, which documents the * props contract as a whole — a genuine load-bearing field, unlike the * vestigial descriptions on actionSpec / streamSpec. * * Symmetry is with {@link JsonSchema.properties}, not with sibling specs. * Implementers walking `DataContract` must special-case `props` vs * `actionSpec` / `streamSpec`. */ export interface PropsSpec { /** Human-readable description of the overall props contract */ description?: string; /** Per-prop definitions keyed by prop name */ properties: Record; } /** * Per-channel state-folding mode. Tells subscribers whether each * delivery on a channel is a new event to accumulate or a full * replacement of the channel's current value. * * Default when omitted on a {@link StreamChannelEntry}: * {@link DEFAULT_STREAM_CHANNEL_MODE} (`'append'`). * * Maps 1:1 to the outbound stream envelope's `mode` field in the * three-channel-topology doctrine. */ export type StreamChannelMode = 'append' | 'replace'; /** * Per-channel replay policy. Declares what a reconnecting subscriber * sees before the live tail resumes. * * Default when omitted on a {@link StreamChannelEntry}: * {@link DEFAULT_STREAM_REPLAY_POLICY} (`'none'`). * * This is a DECLARATION. Replay infrastructure (ring buffer, * resumption tokens) lives in `@ggui-ai/mcp-server-core`. Consumers * MUST NOT assume replay is implemented just because the spec declares * `'latest'` or `'all'`; until the infra ships, the field is advisory. * * - `'latest'` — subscriber sees only the most recent payload for the * channel (useful for state-broadcast channels). * - `'all'` — subscriber sees the full buffered history (useful for * event logs / append-only feeds). * - `'none'` — no replay; subscriber only sees deliveries after * attachment. */ export type StreamReplayPolicy = 'latest' | 'all' | 'none'; /** Locked default applied when {@link StreamChannelEntry.mode} is omitted. */ export declare const DEFAULT_STREAM_CHANNEL_MODE: StreamChannelMode; /** Locked default applied when {@link StreamChannelEntry.replay} is omitted. */ export declare const DEFAULT_STREAM_REPLAY_POLICY: StreamReplayPolicy; /** Locked default applied when {@link StreamChannelEntry.complete} is omitted. */ export declare const DEFAULT_STREAM_CHANNEL_COMPLETE = false; /** * Per-channel metadata in a {@link StreamSpec}. Declares one named * channel's payload contract plus its runtime semantics. * * The payload `schema` is the authoritative contract — every live-channel * enforcement point validates deliveries against it. The semantics * fields (`mode` / `replay` / `complete`) are informational: consumers * that care about them honor them at their own boundary. Default * behavior when a field is omitted is documented per-field. */ export interface StreamChannelEntry { /** Human-readable description of this channel */ description?: string; /** * JSON Schema for the channel payload. This is the authoritative * shape guard for every delivery on this channel — live-channel * enforcement points (hosted fan-out, OSS `/ws`, `@ggui-ai/mcp-apps-react` * data receipt, `@ggui-ai/mcp-apps-react-native` data receipt) all validate * deliveries against it. * * When the channel declares a `source.tool` feed, the protocol-level * `CTR_SCHEMA_INCOMPAT` linter additionally checks that the feed * tool's declared return schema is a subset of this schema — see * the docstring on {@link ActionEntry.schema} for the schema-subset * algorithm and the `schema_mismatch_error` failure surface. */ schema: JsonSchema; /** Example payload (used for documentation and smoke testing). Typed as {@link JsonValue}. */ example?: JsonValue; /** * Client-side state-folding mode. See {@link StreamChannelMode}. When * omitted, consumers SHOULD apply {@link DEFAULT_STREAM_CHANNEL_MODE} * (`'append'`). Not a validator input — informational only. */ mode?: StreamChannelMode; /** * Server-side replay policy. See {@link StreamReplayPolicy}. When * omitted, consumers SHOULD apply {@link DEFAULT_STREAM_REPLAY_POLICY} * (`'none'`). Advisory until the `@ggui-ai/mcp-server-core` * ring-buffer infrastructure ships. */ replay?: StreamReplayPolicy; /** * Declares whether this channel has a terminal completion marker. * When omitted, consumers SHOULD treat the channel as open-ended * (default: {@link DEFAULT_STREAM_CHANNEL_COMPLETE}, `false`). * * Envelope-level plumbing (the outbound envelope's terminal marker) * is NOT wired by the current `StreamSpec` definition — declaring * `complete: true` here is forward-compatible but consumers MUST NOT * assume the envelope carries a completion field until the envelope * work lands. */ complete?: boolean; /** * Optional source declaration — when present, the channel is fed by * a tool called periodically (poll) or subscribed-to (push) by the * runtime. Replaces the retired top-level `broadcast` config. * * `tool` references an `agentCapabilities.tools[*]` key (structural * cross-ref enforced by the protocol linter: `CTR_REF_STREAM_SOURCE`). * `args` are passed on each call. * * Transport selection is NOT in the contract — it's runtime-negotiated * by `@ggui-ai/wire` between WebSocket subscribe (when the server * declares `serverCapabilities.streamWebSocket` AND the tool is in * `streamWebSocketLocalTools`) and iframe polling fallback. */ source?: { /** agentCapabilities.tools key whose tool feeds this channel. */ tool: string; /** Arguments passed to the source tool on each call. */ args?: JsonObject; }; } /** * Stream contract — describes the typed channels the component consumes * on the live render plane (the live channel in the three-channel doctrine). * * Shape: flat map keyed by channel name → entry. * `DataContract.streamSpec[channelName]` IS the entry. * * See the design-lock block above for what each channel declares and * what is explicitly NOT in scope for this shape. */ export type StreamSpec = Record; /** * Per-action metadata in an ActionSpec. Actions are GESTURES — discrete * client-originated events the agent reacts to on its next turn. There * is one and only one routing target: the agent (no synchronous * server-side dispatch). Authors who want a hint about which tool the * agent SHOULD invoke next declare it via the optional `nextStep` field * below. * * Actions without a `schema` have void payload (fire-and-forget). * The `example` field is {@link JsonValue} to accept any JSON-safe sample. */ export interface ActionEntry { /** Human-readable description of this action */ description?: string; /** Label shown on the UI element */ label: string; /** * This action may fire AT MOST ONCE per render (ggui#1108) — declared by * the contract's author, never inferred, because only the author knows. * The runtime suppresses a second dispatch for the render's lifetime — and * names the suppression (never silent; it does not reach the agent as a * dispatch): a fresh `ggui_render` re-arms it, a `ggui_update` of the same * render does not. Sibling of `confirm` (which asks before firing), not a * pair. */ oneShot?: boolean; /** * JSON Schema for the callback payload. Optional — actions without a * `schema` have void payload (fire-and-forget). * * **Author invariant when paired with `nextStep`:** the values * accepted by `ActionEntry.schema` SHOULD be a subset of the values * accepted by the hinted tool's `inputSchema`. The validation is * advisory — the agent owns the actual tool call on its next turn * and is responsible for shaping the payload as the tool expects. * For tools registered on THIS server, the F4 schema-compat checker * surfaces a `schema_mismatch_error` at render-time / blueprint- * registration-time so authors get fail-loud feedback. * * The canonical algorithm lives in * `@ggui-ai/protocol/validation/schema-subset`; zod → JsonSchema * conversion uses `@ggui-ai/protocol/validation/zod-to-json-schema`. * Default policy is `'reject'`; hosts MAY configure `'warn'` or * `'off'` via `CreateGguiServerOptions.schemaCompatCheck`. * * P0 checker scope covers type match, required-set, property * recursion, items recursion, and `additionalProperties`. * Unsupported constructs (`oneOf` / `anyOf` / `enum` / `const` / * `$ref` / `allOf`) are flagged honestly rather than silently * passing — authors using them see a `'unsupported'` violation * reason and the check falls back to operator discipline for * those constructs. P1/P2 algorithm coverage is a follow-up. */ schema?: JsonSchema; /** Example callback payload (used for documentation). Typed as {@link JsonValue}. */ example?: JsonValue; /** Icon hint (emoji or icon name) */ icon?: string; /** * The author marks this action GRAVE (ggui#1112) — ADVISORY, not a rule. * `@ggui-ai/ui-gen`'s contract context renders it to the composing model * as information and the model decides; nothing enforces it mechanically, * which is the same standing `nextStep` has. It asks BEFORE firing, where * {@link ActionEntry.oneShot} bounds how OFTEN it may fire. */ confirm?: boolean; /** * OPTIONAL. Author-declared hint for the agent's next turn — the * `agentCapabilities.tools[*]` key the agent INTENDS to call when * this action fires. The value MUST resolve to a declared * `agentCapabilities.tools` entry on the same contract (cross-ref * invariant `CTR_REF_NEXT_STEP`, enforced by * `@ggui-ai/protocol/validation/cross-references`). * * Hint, not binding. The runtime emits the action as an event; the * agent decides whether to honor the intent on its next turn based * on its broader context (other tools available, user history, etc.). * * When absent, the action is a pure event signal — the agent receives * `{action: , data: }` and decides what to do * unconstrained by author intent. * * Implementations MUST forward `nextStep` as event metadata to the * agent without rejection. If the named tool isn't in the agent's * toolbox at dispatch time, the agent surfaces the gap on its next * turn (typically as `TOOL_UNAVAILABLE`); the protocol does NOT * fail at render. */ nextStep?: string; } /** * Action contract — declarative callbacks the component must wire. * * Shape: flat map keyed by action name → entry. * `DataContract.actionSpec[actionName]` IS the entry. */ export type ActionSpec = Record; /** * Per-tool metadata in an {@link AgentCapabilitiesSpec}, keyed by the * bare MCP tool name (the catalog key) in {@link AgentCapabilitiesSpec.tools}. * * Documents an MCP tool the contract references — by `actionSpec[*].nextStep`, * by `streamSpec[*].source.tool`, or simply for the LLM-authoring catalog. * * Shape: `serverInfo` (OPTIONAL) carries the owning MCP server identity; * `(serverInfo.name, toolName)` is the canonical cross-framework identity and * enters the contract hash. In Tier 2 the LLM derives `serverInfo.name` from * the `mcp____` tool prefix (omit when there is no prefix — never * invent); in Tier 1 the agent-server catalog fills it from `initialize`. * `serverInfo.version` is OPTIONAL metadata, NOT identity (never enters the * hash). `toolInfo` echoes the MCP `tools/list` descriptor (minus `name`, which is the * catalog key). The ggui authoring layer adds `usage` + `example` on top * of the MCP-native descriptor. The `example` field's `input`/`output` * keys align with MCP's tool envelope naming so the contract reads * identically to what the agent's MCP client sees. */ export interface AgentToolEntry { /** Owning MCP server identity. OPTIONAL: populated by the agent-server * catalog-builder (Tier 1) or derived by the LLM from the mcp____ * prefix (Tier 2). (serverInfo.name, toolName) is the canonical identity; * `version` is OPTIONAL metadata (catalog fills it; prefix-authoring omits it) * and is stripped from the canonical hash. */ serverInfo?: { name: string; version?: string; }; /** MCP tool descriptor, echoed from `tools/list` (minus name = the catalog key). */ toolInfo: { /** JSON Schema for the tool input. REQUIRED — every MCP tool has one. */ inputSchema: JsonSchema; description?: string; outputSchema?: JsonSchema; }; /** ggui authoring layer (not MCP): when/why/by-whom the tool is called. */ usage?: string; example?: { input: JsonValue; output: JsonValue; }; } /** * Agent-capabilities catalog — declares the MCP tools the contract references. * * The agent's MCP toolbox is the source of truth at dispatch time; this * catalog is the **contract author's documentation** of which tools the * UI relies on. Cross-referenced from: * * - `actionSpec[*].nextStep` (agent's next-turn hint) * - `streamSpec[*].source.tool` (channel data source) * * Shape mirrors {@link ClientCapabilitiesSpec} — both are capability * catalogs grouped under a `*Capabilities` parent so the protocol's * capability namespace reads symmetrically (agent-side tools vs. * client-side gadgets). */ export interface AgentCapabilitiesSpec { /** Per-tool definitions keyed by tool name. */ tools: Record; } /** * Per-export metadata shared by every {@link GadgetExport} kind — * LLM-targeted teaching text plus the runtime gates an export needs. * * Required-ness lives in the schemas, not the type system: the * registry-side `strictGadgetExportSchema` requires `description` / * `usage` / `example`; the wire-permissive `gadgetExportSchema` * leaves them optional. */ export interface GadgetExportBase { /** * Human-readable description of what this export does. REQUIRED on * the registry side; optional on the contract side (render-time merge * inherits the registry copy when absent). */ description?: string; /** * When / why / by-whom this export is used — the free-form * "context-of-use" hint bare `description` lacks. Parallel to * {@link AgentToolEntry.usage}. */ usage?: string; /** * Concrete usage example for boilerplate generation + prompt * priming. Free-form `JsonValue` (typically an object describing * the call / render shape + expected return). */ example?: JsonValue; /** * Anti-patterns + known gotchas surfaced in code-gen prompts so the * LLM avoids the same traps every time. */ gotchas?: string; /** * Optional permission identifier this export gates on (Web * Permissions API + MCP Apps enum — see `KNOWN_PERMISSION_NAMES`). * The registry-side schema enum-checks it; the wire side never * carries it. */ permission?: string; } /** * A hook export — a `use`-prefixed React hook the generated component * calls. Implementations MUST satisfy {@link GadgetHook}. */ export interface GadgetHookExport extends GadgetExportBase { /** * Hook name — `use`-prefixed camelCase (e.g. `'useLeafletMap'`, * `'useGeolocation'`). Boilerplate emits `import { } from * ''` plus a call site against this value. */ hook: string; /** * Mutually exclusive with {@link GadgetComponentExport.component}. * `component?: never` makes {@link GadgetExport} a type-EXCLUSIVE * union — a both-fields object `{hook, component}` no longer * type-checks, so field-presence kind discrimination is order- * independent. */ component?: never; } /** * A component export — a PascalCase React component the generated * code renders as JSX (``). */ export interface GadgetComponentExport extends GadgetExportBase { /** * Component name — PascalCase (e.g. `'Chart'`, `'MapView'`). * Boilerplate emits `import { } from ''` plus * a JSX render site against this value. */ component: string; /** * Mutually exclusive with {@link GadgetHookExport.hook}. * `hook?: never` makes {@link GadgetExport} a type-EXCLUSIVE union — * a both-fields object `{hook, component}` no longer type-checks, so * field-presence kind discrimination is order-independent. */ hook?: never; } /** * One export of a gadget package — a hook or a component, * distinguished by which identifier field is present (`hook` vs * `component`). A gadget package ({@link GadgetDescriptor}) bundles * one or more of these behind a single npm identity; a wire-side * {@link GadgetExportUse} entry points at exactly one. */ export type GadgetExport = GadgetHookExport | GadgetComponentExport; /** * **Wire-side** per-export use entry — one value in a package's * {@link GadgetPackageUse} map on * `DataContract.clientCapabilities.gadgets`. * * The export NAME is the map key, not a field — and its grammar * discriminates kind (a `use`-prefixed key is a hook, a PascalCase * key is a component). The only wire-authored payload is optional * intent-specific override prose. * * Design intent (S+ protocol bar): the wire carries IDENTITY ONLY — * `(package, export name)`. It CANNOT carry `version`, transport * fields (`bundleUrl`, `bundleSri`, `bundleHost`, `connect`, * `requires`, `typesUrl`, …) or per-export registry metadata * (`permission`, `example`, `gotchas`). All of that belongs to the * registered {@link GadgetDescriptor} the ggui server resolves from * the app's `App.gadgets` catalog at render time — `version` is the * operator's deployment pin, not the agent's to author. */ export interface GadgetExportUse { /** * Intent-specific override of the registered export's description. * When omitted, render-time resolution inherits the registered * description verbatim; when present, the agent's prose wins. */ description?: string; /** * Intent-specific override of the registered usage hint. Same * "agent wins" merge semantics as `description`. */ usage?: string; } /** * **Wire-side** per-package gadget use — the value type of * {@link ClientCapabilitiesSpec.gadgets}, which is keyed by npm * package name. * * A map of export name → {@link GadgetExportUse} — the exports of one * package the UI uses, keyed by export name (≥1; a `use`-prefixed * hook or a PascalCase component). The wire carries no package-level * field — `version` and transport metadata are registry-side — so a * package entry IS its export map, with no `exports` wrapper. */ export type GadgetPackageUse = Record; /** * Flattened view of one gadget export a contract uses — produced by * `listContractGadgets` from the package-keyed * {@link ClientCapabilitiesSpec.gadgets}. * * NOT a wire type: an internal convenience so the render gates, the * descriptor resolver, and code-gen can iterate `(package, name)` * pairs uniformly instead of re-walking the nested wire map. */ export interface GadgetUse { /** npm package name — the `clientCapabilities.gadgets` map key. */ package: string; /** Export name — `use`-prefixed hook or PascalCase component. */ name: string; /** Intent-specific description override, when the contract set one. */ description?: string; /** Intent-specific usage override, when the contract set one. */ usage?: string; } /** * Registered descriptor for a gadget **package** (registry side). * * A gadget package bundles one or more {@link GadgetExport}s — hooks * and/or components — behind a single npm identity (`package` + * `version`) and a single bundle. Transport metadata (`bundleUrl`, * `bundleSri`, `bundleHost`, `styleUrl`, `connect`, `requires`, * `typesUrl`, `typesSri`) is per-PACKAGE; teaching text + `permission` * are per-EXPORT (on each `exports[*]`). * * One shape used by: * * - **Registry side** (`App.gadgets` + wrapper SDK output) — every * export's `description` / `usage` / `example` SHOULD be * populated. `strictGadgetDescriptorSchema` enforces required * teaching text + an enum-tight `permission` per export; * `registeredGadgetDescriptorSchema` additionally requires * `typesUrl` for non-stdlib packages. * - **Resolved sidecar side** — at render time * `filterDescriptorsToContract` snapshots the subset of * `App.gadgets` the contract references onto * `ComponentGguiSession.gadgetDescriptors`. Wire-side authors NEVER * see this shape; they author the package-keyed * {@link ClientCapabilitiesSpec} map of {@link GadgetPackageUse}. * * Strictness lives in the schemas, not the type system. * * See {@link GadgetHook} for the runtime hook contract every hook * export MUST satisfy. */ export interface GadgetDescriptor { /** * The exports this package provides — hooks and/or components. At * least one (enforced by the schema). Each {@link GadgetExport} * carries its own identifier (`hook` or `component`) + teaching text * (`description` / `usage` / `example` / `gotchas`) + per-export * `permission`. */ exports: GadgetExport[]; /** * Exact semver pin (e.g., `'0.0.1'`, `'1.2.3-beta.1'`). REQUIRED. * Registry-side ONLY — the wire carries no version; the operator's * `App.gadgets` catalog is the sole version pin, resolved * server-side at render time. `(package, version)` is the registry's * frozen identity tuple. Bumping requires a new `bundleSri` / * `typesSri` (registry-immutability invariant enforced by * `lintGadgetCatalog`). * * No ranges (no `^`, `~`, `>=`). */ version: string; /** * Bare npm package name the wrapper is imported from (e.g., * `'@my-org/leaflet'`, `'@ggui-ai/gadgets'`). REQUIRED. The wire * references this package by name — it is the key of the * `clientCapabilities.gadgets` map; `(package, version)` is the * registry's frozen identity tuple. * * Boilerplate emits `import { } from '';` against * this value. NOT a URL — registry hostnames live on `bundleUrl` / * `typesUrl`. The gadget author bundles all underlying 3rd-party * dependencies into the wrapper bundle. */ package: string; /** * ggui-hosted bundle URL — the preferred distribution path. Same * origin as the iframe in single-deployment installs (served * from `/_ggui/libs//bundle.js`) and the ggui marketplace * CDN in cloud deployments. CSP `script-src` allowlists only the * ggui origin — no per-plugin third-party origins. * * When set, the boilerplate generator imports from this URL * instead of `package`. Either `package` OR `bundleUrl` MUST be * present. * * Escape hatch: authors who want CDN-distributed bundles can point * `bundleUrl` at a 3rd-party URL (e.g., `'https://esm.sh/...'`) * and accept that origin in the CSP allowlist. The preferred path * is to publish to ggui's bundle host and stay same-origin. */ bundleUrl?: string; /** * Registry hostname (no scheme, no path) the server uses to resolve * `bundleUrl` + `styleUrl` at render time: * * `https:///bundles/public////bundle.js` * `https:///bundles/public////style.css` * * (the PUBLIC prefix of the registry's visibility-split bundle * layout — host composition serves the anonymous render-time fetch; * private artifacts always arrive as explicit `bundleUrl` values) * * Resolution order (operator wins over author wins over spec default): * * 1. operator's `app.gadgets[*].bundleUrl` — explicit full URL, * escape hatch that bypasses bundleHost resolution entirely. * 2. operator's `app.gadgets[*].bundleHost` — hostname override * for e2e / sandbox testing. * 3. gadget author's `ggui.gadget.json#bundleHost` (default the * author shipped). * 4. spec default `registry.ggui.ai`. * * Resolution requires `package` (`@scope/name`) and `version` on the * same entry — without them the server cannot assemble the path. * The `strictGadgetDescriptorSchema` refinement enforces this trio. * * Hostname-only constraint: lowercase alphanumerics + dots/hyphens + * optional `:port`. See {@link BUNDLE_HOST_RE}. Non-HTTPS or * non-standard paths require the `bundleUrl` escape hatch instead. */ bundleHost?: string; /** * SHA-384 SRI hash of the bundle, formatted as `sha384-`. * When present, iframe-runtime emits the bundle import as a * `