@cyberart-io/engine 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +305 -1
- package/dist/headless.d.ts +2494 -37
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +2589 -267
- package/dist/index.js +1 -1
- package/docs/asset-resolver.md +1 -1
- package/docs/browser-harness.md +1 -1
- package/docs/capability-manifest.md +1 -1
- package/docs/content-revision.md +64 -0
- package/docs/events.md +1 -1
- package/docs/headless-harness.md +1 -1
- package/docs/job-orchestration.md +55 -0
- package/docs/portal-lifecycle.md +50 -0
- package/docs/presentation-bindings.md +93 -0
- package/docs/presentation-sequences.md +150 -0
- package/docs/production-scenario.md +81 -0
- package/docs/remote-cart-manifest.md +66 -0
- package/docs/replay-inspector.md +1 -1
- package/docs/runtime-group.md +1 -1
- package/docs/selection-trace.md +60 -0
- package/docs/semantic-layers.md +72 -0
- package/docs/snapshots.md +1 -1
- package/docs/visual-layers.md +1 -1
- package/docs/world-graph.md +39 -0
- package/docs/world-patch.md +61 -0
- package/package.json +3 -2
package/docs/asset-resolver.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Host-owned URL policy. The engine caches, dedupes, tracks progress, applies timeouts and fallbacks, and (in live mode) dispatches `ASSET_READY_EVENT` / `ASSET_FAILED_EVENT`. Authored carts keep logical refs (`moltazine:post/<id>#primary-image`, `world:asset/…`, `library:…`, or ordinary URLs). the host, Cyberart, and a local preview each supply a different `AssetResolver`.
|
|
4
4
|
|
|
5
|
-
Deterministic timing: [deterministic mode](deterministic-mode.md). CI: [headless harness](headless-harness.md). Event mailbox: [events](events.md).
|
|
5
|
+
Deterministic timing: [deterministic mode](deterministic-mode.md). CI: [headless harness](headless-harness.md). Event mailbox: [events](events.md). Built on this: [remote cart manifests](remote-cart-manifest.md) (`createDeclaredAssetResolver`, declared URLs and hashes only), [content revision](content-revision.md) (resolves every declared asset before one atomic bundle swap), [selection trace](selection-trace.md) (records which logical ref, hash, and fallback won).
|
|
6
6
|
|
|
7
7
|
Back to the [package README](../README.md).
|
|
8
8
|
|
package/docs/browser-harness.md
CHANGED
|
@@ -4,7 +4,7 @@ Opt-in harness that mounts a caller-supplied host into a real viewport, optional
|
|
|
4
4
|
|
|
5
5
|
Import `createBrowserHarness` from **`@cyberart-io/engine`**. It does not load `node:fs`. In Vitest/jsdom, call `installHeadlessCanvas()` from **`@cyberart-io/engine/headless`** first so `Canvas2D` pixels exist. Production Player / kaleidoscope should not call this harness.
|
|
6
6
|
|
|
7
|
-
Related: [normalized geometry](normalized-geometry.md), [presentation adapter](presentation-adapter.md), [compositor](compositor.md), [headless harness](headless-harness.md).
|
|
7
|
+
Related: [normalized geometry](normalized-geometry.md), [presentation adapter](presentation-adapter.md), [compositor](compositor.md), [headless harness](headless-harness.md). Built on this: [production scenario](production-scenario.md) (mounts the full host/runtime/cart composition through this harness), [semantic layers](semantic-layers.md) (`semanticLayers` option, a11y snapshot).
|
|
8
8
|
|
|
9
9
|
Back to the [package README](../README.md).
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Capability manifest
|
|
2
2
|
|
|
3
|
-
Versioned JSON for what a cart needs from the host: runtime contract, lifecycle phases, managers, asset kinds, event patterns, permissions, and integrations. Related: [events](events.md) (emit / subscribe patterns), [asset resolver](asset-resolver.md) (kinds only — this file does not resolve URLs), [calculation carts](calculation-carts.md) (`kind`).
|
|
3
|
+
Versioned JSON for what a cart needs from the host: runtime contract, lifecycle phases, managers, asset kinds, event patterns, permissions, and integrations. Related: [events](events.md) (emit / subscribe patterns), [asset resolver](asset-resolver.md) (kinds only — this file does not resolve URLs), [calculation carts](calculation-carts.md) (`kind`). Built on this: [remote cart manifests](remote-cart-manifest.md) (signed catalog with optional nested `capabilities` + host `grants`), [content revision](content-revision.md) (required grants checked before activation).
|
|
4
4
|
|
|
5
5
|
Back to the [package README](../README.md).
|
|
6
6
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Content revision activation
|
|
2
|
+
|
|
3
|
+
Atomic staging and commit for an external content revision (room art, masks, portraits, line audio). Adapters load catalogs; the activator resolves every declared asset and swaps one complete bundle. Consumers see the last known good revision or the new revision — never a mix.
|
|
4
|
+
|
|
5
|
+
Related: [Asset resolver](asset-resolver.md) (`createDeclaredAssetResolver`), [Remote cart manifests](remote-cart-manifest.md) (`verifyRemoteCartSignature`, grants), [Capability manifest](capability-manifest.md), [Snapshots](snapshots.md). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-79-content-revision.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createContentRevisionActivator` from `@cyberart-io/engine` | Persist `snapshot()` in envelope `hostState`. Read `activeBundle()` / `inspect()` for presentation. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | `createStaticContentAdapter` plus in-process `createRemoteContentAdapter({ fetch })`. |
|
|
19
|
+
|
|
20
|
+
Moltazine World is the first **adapter**, not a hard-coded SDK. Tests inject a fake remote fetch. There is no product UI here.
|
|
21
|
+
|
|
22
|
+
## Contract
|
|
23
|
+
|
|
24
|
+
1. **Identity.** A bundle is `contentId` + exact `revision` + `manifestVersion` + asset hashes + publisher/source + adapter id. Snapshots never store `"latest"`.
|
|
25
|
+
2. **Adapters.** `createStaticContentAdapter` (local catalog) and `createRemoteContentAdapter` (injected fetch, signed CYB-74 JSON, optional `{ world, revision, manifest }` envelope) implement the same load/activation contract. Static `discover` picks the numeric-aware newest revision id (`10` beats `9`).
|
|
26
|
+
3. **Staging.** Resolve every declared asset, validate schema, hashes, signatures, and required grants **before** `activeBundle()` changes.
|
|
27
|
+
4. **Commit.** One atomic swap, exactly one `content.revision.state.activated` event per accepted revision. Idempotent activate of the live bundle/revision id does not emit again.
|
|
28
|
+
5. **Visibility.** `inspect()` / `activeBundle()` return a complete revision or `null`. Staging identity may appear on `inspect().staging`; resources stay on the live bundle.
|
|
29
|
+
6. **Rollback.** Last-known-good is retained. Hash/schema/capability/health-check failure keeps or restores that bundle and emits a machine-readable rejection. `rollback()` is idempotent.
|
|
30
|
+
7. **Optional assets** may omit or use a declared hashed fallback. Required assets must all succeed.
|
|
31
|
+
8. **Supersede.** A newer `activate` aborts in-flight staging, emits `superseded`, then stages the new revision. A stale health check after a newer commit returns `stale-health-check`, not `superseded`.
|
|
32
|
+
9. **Pin.** `pin()` freezes live movement after a bundle is active: `activate` of another revision, `rollback`, and `restore` that would change the live bundle all fail with `pinned`. Discover still works. Idempotent activate/rollback of the current bundle is allowed.
|
|
33
|
+
10. **Restore.** Validate the entire snapshot (schema version, exact revisions known to adapters) before mutate. Unknown version or unknown revision leaves the live bundle unchanged.
|
|
34
|
+
11. **Cache.** Byte cache is keyed by digest and re-hashed on reuse.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {
|
|
38
|
+
createContentRevisionActivator,
|
|
39
|
+
createRemoteContentAdapter,
|
|
40
|
+
createStaticContentAdapter,
|
|
41
|
+
} from '@cyberart-io/engine';
|
|
42
|
+
|
|
43
|
+
const activator = createContentRevisionActivator({
|
|
44
|
+
adapters: [staticAdapter, remoteAdapter],
|
|
45
|
+
grants: createHostGrantSet(['audio']),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await activator.activate({ contentId: 'room-overlook', revision: '8' });
|
|
49
|
+
activator.activeBundle(); // complete revision 8, or null
|
|
50
|
+
activator.snapshot(); // exact revision + hashes for hostState
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| Event | When |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `content.revision.state.discovered` | Adapter reported an exact revision |
|
|
56
|
+
| `content.revision.state.staging` | Resolve/validate started; presentation unchanged |
|
|
57
|
+
| `content.revision.state.validated` | Declared assets, hashes, and capabilities passed |
|
|
58
|
+
| `content.revision.state.rejected` | Staging or validation failed; live bundle unchanged |
|
|
59
|
+
| `content.revision.state.activated` | Atomic commit of one complete bundle |
|
|
60
|
+
| `content.revision.state.rolled-back` | Health-check/activation failure or host `rollback()` |
|
|
61
|
+
| `content.revision.state.superseded` | Newer revision cancelled this staging attempt |
|
|
62
|
+
| `content.revision.diagnostic.lifecycle` | Machine-readable rejection detail |
|
|
63
|
+
|
|
64
|
+
Contracts: `contentRevisionEventContracts()`.
|
package/docs/events.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Events and router
|
|
2
2
|
|
|
3
|
-
Mailbox (one runtime) and `createEventRouter` (many carts). Prefer `createRuntimeGroup` when several carts should share one router and lockstep clock. Carts never receive the router object or another cart’s `HostChannel`. Related: [deterministic mode](deterministic-mode.md) (`now` / `createId` / `turn` per `step`), [presentation adapter](presentation-adapter.md) (`presentation.state.model`), [presentation cue](presentation-cue.md) (frame-local lifecycle, not routed unless you define a contract), [runtime group](runtime-group.md), [capability manifest](capability-manifest.md), [replay inspector](replay-inspector.md).
|
|
3
|
+
Mailbox (one runtime) and `createEventRouter` (many carts). Prefer `createRuntimeGroup` when several carts should share one router and lockstep clock. Carts never receive the router object or another cart’s `HostChannel`. Related: [deterministic mode](deterministic-mode.md) (`now` / `createId` / `turn` per `step`), [presentation adapter](presentation-adapter.md) (`presentation.state.model`), [presentation cue](presentation-cue.md) (frame-local lifecycle, not routed unless you define a contract), [runtime group](runtime-group.md), [capability manifest](capability-manifest.md), [replay inspector](replay-inspector.md). Modules that publish typed `*.state.*` / `*.diagnostic.*` contracts through this router: [content revision](content-revision.md), [job orchestration](job-orchestration.md), [world graph](world-graph.md), [world patch](world-patch.md), [production scenario](production-scenario.md), [presentation bindings](presentation-bindings.md), [presentation sequences](presentation-sequences.md).
|
|
4
4
|
|
|
5
5
|
Back to the [package README](../README.md).
|
|
6
6
|
|
package/docs/headless-harness.md
CHANGED
|
@@ -4,7 +4,7 @@ CI / agent wrapper around production `createRuntime({ deterministic })`. Not a s
|
|
|
4
4
|
|
|
5
5
|
Import from **`@cyberart-io/engine/headless`**. Production carts and browser hosts keep using `@cyberart-io/engine` so Vite never resolves `node:fs/promises`.
|
|
6
6
|
|
|
7
|
-
Depends on [deterministic mode](deterministic-mode.md). Host reducers: [events](events.md). Presentation overlay: [presentation adapter](presentation-adapter.md). Several carts: [runtime group](runtime-group.md) (`createHeadlessMultiCartHarness`).
|
|
7
|
+
Depends on [deterministic mode](deterministic-mode.md). Host reducers: [events](events.md). Presentation overlay: [presentation adapter](presentation-adapter.md). Several carts: [runtime group](runtime-group.md) (`createHeadlessMultiCartHarness`). Whole production composition in CI: [production scenario](production-scenario.md). Headless fakes for the framework modules also live on this entry: `createHeadlessAudioAdapter` ([presentation sequences](presentation-sequences.md)), `createHeadlessJobWorker` ([job orchestration](job-orchestration.md)).
|
|
8
8
|
|
|
9
9
|
Back to the [package README](../README.md).
|
|
10
10
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Job orchestration
|
|
2
|
+
|
|
3
|
+
Durable lifecycle for long-running generated work (world chunks, media transforms). The coordinator models queueing, progress, retries, evaluator feedback, leases, and host-owned apply. Workers perform the task; wall-clock completion never mutates authoritative host state.
|
|
4
|
+
|
|
5
|
+
Related: [Events and router](events.md) (CYB-59 contracts), [Replay inspector](replay-inspector.md) (`DEFAULT_SENSITIVE_KEYS` / `REDACTED_VALUE`), [Snapshots](snapshots.md). Built on this: [World patch](world-patch.md) (a `ready` job result becomes one transactional world revision under host `commit`), [World graph](world-graph.md) (generated frontier work). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-80-job-orchestration.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createJobCoordinator` from `@cyberart-io/engine` | Persist `snapshot()` in envelope `hostState`. Call `accept(jobId)` only under host policy. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | `createHeadlessJobWorker` controls complete/fail timing. `createMemoryJobPersistence` is enough for refresh tests. |
|
|
19
|
+
|
|
20
|
+
## Contract
|
|
21
|
+
|
|
22
|
+
1. **Definitions** are versioned: request schema, result kind, retry/lease/timeout, declared fallback.
|
|
23
|
+
2. **Submit** with a stable idempotency key and a correlation id for the initiating player intent. Duplicate keys return the same job.
|
|
24
|
+
3. **States:** queued → claimed → awaiting-evaluation → ready → applied, plus retry-scheduled, failed, canceled, superseded.
|
|
25
|
+
4. **Results** are refs (`uri` + `kind`), not embedded media/world payloads.
|
|
26
|
+
5. **Evaluator** may accept, reject, or request a bounded revision.
|
|
27
|
+
6. **Host apply** is `accept(jobId)` on `ready` only. Worker `complete` stops at awaiting-evaluation.
|
|
28
|
+
7. **Restore** validates JSON and schema version before mutate. Tampered snapshots return `{ ok: false }` and leave jobs unchanged.
|
|
29
|
+
8. **Export traces** and replay-inspector payloads redact `JOB_REDACTED_KEYS` / `DEFAULT_SENSITIVE_KEYS` (`prompt`, `credentials`, `credential`, `authorization`, `apiKey`, `secret`) via `REDACTED_VALUE`. `token` is not in that set — pass it in `createReplayInspector({ redactedKeys: ['token'] })` when a cart uses that name. Reason codes stay visible.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import {
|
|
33
|
+
createHeadlessJobWorker,
|
|
34
|
+
createJobCoordinator,
|
|
35
|
+
} from '@cyberart-io/engine/headless';
|
|
36
|
+
|
|
37
|
+
const worker = createHeadlessJobWorker();
|
|
38
|
+
const jobs = createJobCoordinator({
|
|
39
|
+
definitions: [frontierDefinition],
|
|
40
|
+
worker,
|
|
41
|
+
now: () => clock,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
jobs.submit({
|
|
45
|
+
definitionId: 'frontier-generate',
|
|
46
|
+
idempotencyKey: 'frontier:day-1',
|
|
47
|
+
request: { prompt: '...', seed },
|
|
48
|
+
correlationId: playerIntentId,
|
|
49
|
+
});
|
|
50
|
+
jobs.claim('worker-1');
|
|
51
|
+
worker.complete(jobId, { schemaVersion: 1, kind: 'world-chunk', uri: 'ref://...' });
|
|
52
|
+
jobs.evaluate(jobId, 'accept');
|
|
53
|
+
// host policy: e.g. only at the day boundary
|
|
54
|
+
jobs.accept(jobId);
|
|
55
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Portal lifecycle
|
|
2
|
+
|
|
3
|
+
One enter / exit / abort contract for leaving a cart, inhabiting another exclusively, and returning a versioned outcome. Arcade cabinet, painting, dream, wormhole, book, and nested world are host metaphors over the same primitive — not separate lifecycles.
|
|
4
|
+
|
|
5
|
+
Related: [runtime group](runtime-group.md) (per-participant suspend), [remote cart manifests](remote-cart-manifest.md) (`HostGrantSet` transfer), [snapshots](snapshots.md). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-75-portal-lifecycle.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `createPortalLifecycle`, `cabinetPortal`, `paintingPortal` from `@cyberart-io/engine` | Chrome / back-stack / a11y stay on the host. Carts declare portal targets and outcome schema versions. |
|
|
18
|
+
| Vitest / jsdom | same, or from `@cyberart-io/engine/headless` | Pair with `createRuntimeGroup` after `installHeadlessCanvas()`. |
|
|
19
|
+
|
|
20
|
+
`cabinetPortal` / `paintingPortal` only stamp `metaphor`. They call the same `enter` / `exit` / `abort`.
|
|
21
|
+
|
|
22
|
+
## Contract
|
|
23
|
+
|
|
24
|
+
1. **Enter** another cart (exclusive presentation).
|
|
25
|
+
2. Pass seed/clock/state/persistence and transfer exclusive grants (`audio`, `controller`, `fullscreen`) for the child's lifetime.
|
|
26
|
+
3. **Suspend** the parent: group `step` skips it; cart state stays snapshot-safe.
|
|
27
|
+
4. **Exit** with `{ schemaVersion: 1, kind: 'completed' | 'aborted', payload? }`.
|
|
28
|
+
5. Nested enter is stack-ordered. Aborting the top frame does not rewrite ancestor `parentState`.
|
|
29
|
+
6. Restore validates JSON and participant ids before mutate. A mid-portal snapshot restores parent-suspended + child-active, reapplies extra child grants, and transfers audio broker authorization the same way enter does. Tampered or unknown-id JSON returns `{ ok: false }` and leaves suspend flags and broker grants unchanged.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import {
|
|
33
|
+
cabinetPortal,
|
|
34
|
+
createPortalLifecycle,
|
|
35
|
+
paintingPortal,
|
|
36
|
+
} from '@cyberart-io/engine';
|
|
37
|
+
|
|
38
|
+
const portal = createPortalLifecycle({
|
|
39
|
+
group,
|
|
40
|
+
grants: { lobby: lobbyGrants, game: gameGrants },
|
|
41
|
+
rootId: 'lobby',
|
|
42
|
+
declarations: [{ id: 'lobby', targets: ['game'] }],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
cabinetPortal(portal).enter({ from: 'lobby', to: 'game', seed });
|
|
46
|
+
const outcome = portal.exit({ score: 108 });
|
|
47
|
+
paintingPortal(portal).enter({ from: 'lobby', to: 'gallery' });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Host-owned chrome reads `portal.stack()` and `portal.activeId()`. Persist `portal.snapshot()` inside envelope `hostState`.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Presentation bindings
|
|
2
|
+
|
|
3
|
+
Declarative, versioned mapping from a host-supplied state projection onto presentation resources (visual versions, presence, props, semantic regions, hotspots, sequences, assets). The host reducer stays authoritative. Cyberart evaluates a bounded manifest and applies **one complete presentation revision** — or the declared fail-closed defaults. Never a mixed scene.
|
|
4
|
+
|
|
5
|
+
Related: [Visual layers](visual-layers.md), [Semantic layers](semantic-layers.md), [Presentation sequences](presentation-sequences.md), [Events](events.md), [Snapshots](snapshots.md). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-83-presentation-bindings.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `definePresentationBindings` / `createPresentationBindingRuntime` from `@cyberart-io/engine` | Pass a JSON projection into `apply`. Persist `snapshot()` in envelope `hostState`. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | Headless and browser entrypoints evaluate the same winners. |
|
|
19
|
+
|
|
20
|
+
The framework does not own inventory, doors, or character location. Hosts define typed projections (CYB-59). Generic fixture ids only: `room`, `overlay`, `whistle`, `door`, `npc`, `lobby`, `hotspot-whistle`, `hotspot-door`, `line-enter`.
|
|
21
|
+
|
|
22
|
+
## Contract
|
|
23
|
+
|
|
24
|
+
1. **Manifest.** `schemaVersion` `1`, stable binding ids, JSON only. Callbacks and `eval` are rejected at define time.
|
|
25
|
+
2. **Predicates.** Constrained expressions over dotted paths (`eq` / `neq` / `present`) or registered typed selectors. No ambient store access.
|
|
26
|
+
3. **Targets.** Visual-layer versions, presence, props, semantic regions, hotspot enablement, sequences, asset bindings.
|
|
27
|
+
4. **Conflicts.** Higher priority wins. Equal priority on the same target is a define-time error. Defaults must cover every target key bindings can select.
|
|
28
|
+
5. **Atomic apply.** Evaluate the full target set, preflight live resources, then apply. `visual.play` / `sequences.playSequence` results are honored: `{ ok: false, reason: 'duplicate' }` is ignored (`onDuplicate: 'ignore'`); other failures abort as `unknown-resource`. On apply failure, controller snapshots are restored and `liveTargets` is not committed. Invalid/missing projections apply the **complete** default set. Restore applies targets before writing revision / selectedBindingIds / override; a failed apply leaves those fields and live presentation unchanged.
|
|
29
|
+
6. **Change minimization.** Unchanged targets are not replayed or remounted. An already-selected matching sequence is not restarted when unrelated projection fields change.
|
|
30
|
+
7. **Overrides.** `setOverride` can change a11y labels / reduced-sensory presentation without mutating the projection or selected binding ids.
|
|
31
|
+
8. **Snapshot.** Records projection revision + selected binding ids + resolved targets. Restore validates schema before mutate.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {
|
|
35
|
+
createPresentationBindingRuntime,
|
|
36
|
+
definePresentationBindings,
|
|
37
|
+
} from '@cyberart-io/engine';
|
|
38
|
+
|
|
39
|
+
const defined = definePresentationBindings({
|
|
40
|
+
id: 'lobby-room',
|
|
41
|
+
schemaVersion: 1,
|
|
42
|
+
bindings: [
|
|
43
|
+
{
|
|
44
|
+
id: 'night-room',
|
|
45
|
+
priority: 30,
|
|
46
|
+
when: { path: 'timeOfDay', eq: 'night' },
|
|
47
|
+
targets: [{ kind: 'visual-layer', layerId: 'room', version: 'night' }],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
defaults: [{ kind: 'visual-layer', layerId: 'room', version: 'day' }],
|
|
51
|
+
});
|
|
52
|
+
if (!defined.ok) throw new Error(defined.errors.map((e) => e.detail).join('; '));
|
|
53
|
+
|
|
54
|
+
const runtime = createPresentationBindingRuntime({
|
|
55
|
+
manifest: defined.manifest,
|
|
56
|
+
visual,
|
|
57
|
+
semantic,
|
|
58
|
+
sequences,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
runtime.apply(
|
|
62
|
+
{ timeOfDay: 'night', inventory: { loonWhistle: false } },
|
|
63
|
+
{ revision: 83 },
|
|
64
|
+
);
|
|
65
|
+
runtime.inspect(); // winners + explanation
|
|
66
|
+
runtime.snapshot(); // revision + selectedBindingIds for hostState
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`createPresentationBindingRuntime` re-validates the manifest and throws if it is invalid.
|
|
70
|
+
|
|
71
|
+
## Predicates
|
|
72
|
+
|
|
73
|
+
| Form | Match |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `{ path: "inventory.loonWhistle", eq: false }` | dotted path equals a JSON primitive |
|
|
76
|
+
| `{ path: "door.roomSeven", neq: "locked" }` | path exists and is not the value, or is missing |
|
|
77
|
+
| `{ path: "timeOfDay", present: true }` | path exists and is not `null` |
|
|
78
|
+
| `{ selector: "isNight" }` | host-registered own-property `(projection) => boolean`. Inherited keys such as `constructor` do not match. |
|
|
79
|
+
| `{ all: [...] }` / `{ any: [...] }` / `{ not: ... }` | boolean combinations |
|
|
80
|
+
|
|
81
|
+
Unknown fields on the manifest, bindings, predicates, or targets fail closed at define time.
|
|
82
|
+
|
|
83
|
+
## Apply, explanation, restore
|
|
84
|
+
|
|
85
|
+
`apply(projection, { revision })` returns `selectedBindingIds`, `explanation` (considered / winners / rejected alternatives / fallback reason), and `inspect()`. Visual swaps use `visual.play` with `durationFrames: 0` so one accepted revision is one coherent layer set. Play results are checked: duplicate-ignore is ok, unknown version / invalid / busy abort and roll the visual / semantic / sequence controllers back. Semantic presence/focus is `setRegionState`. `hitTestEnabled` (and hotspot `enabled` when a `regionId` is set) writes the region's hit-test policy through `semantic.restore`: `false` becomes `pass-through`, `true` restores the region's prior absorbing policy. Sequences use `playSequence` / `skip`. `restore` calls `applyTargets` before committing revision, selected binding ids, or override. `setOverride({ reducedSensory: true })` skips an active bound sequence; clearing the override and reapplying the same projection starts it again.
|
|
86
|
+
|
|
87
|
+
| Event | When |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `presentation.binding.state.applied` | One complete presentation revision committed |
|
|
90
|
+
| `presentation.binding.state.rejected` | Preflight or apply failed; live presentation unchanged |
|
|
91
|
+
| `presentation.binding.diagnostic.lifecycle` | Machine-readable rejection detail |
|
|
92
|
+
|
|
93
|
+
Contracts: `presentationBindingEventContracts()`.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Presentation sequences
|
|
2
|
+
|
|
3
|
+
One frame-stepped clock for a semantic beat that spans audio, captions/text, semantic-region state, visual layers, and typed cues. Hosts pass content through bindings. The engine does not own dialogue graphs, relationship state, inventory, or game rules.
|
|
4
|
+
|
|
5
|
+
Related: [presentation cue](presentation-cue.md), [audio](audio.md), [semantic layers](semantic-layers.md), [visual layers](visual-layers.md), [events](events.md). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-78-presentation-sequence.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `definePresentationSequence` / `createPresentationSequencePlayer` from `@cyberart-io/engine` | Author JSON sequences; `playSequence` with bindings. |
|
|
18
|
+
| Vitest / jsdom | same, plus `createHeadlessAudioAdapter` from `@cyberart-io/engine/headless` | Silent cue traces; `inspectCueIntent` without audible playback. |
|
|
19
|
+
|
|
20
|
+
Generic ids only: `speaker`, `caption`, `room-bg`, `line-1`. Drive `step` from the same clock as deterministic `cart.step`. There are no `setTimeout` / rAF timers.
|
|
21
|
+
|
|
22
|
+
## `definePresentationSequence({ id, tracks, interruptionPolicy, fallbacks })`
|
|
23
|
+
|
|
24
|
+
Authored content must be JSON-serializable. Callbacks and ambient host access are rejected at define time.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import {
|
|
28
|
+
definePresentationSequence,
|
|
29
|
+
createPresentationSequencePlayer,
|
|
30
|
+
} from '@cyberart-io/engine';
|
|
31
|
+
|
|
32
|
+
const defined = definePresentationSequence({
|
|
33
|
+
id: 'spoken-line',
|
|
34
|
+
interruptionPolicy: 'replace', // 'replace' | 'queue' | 'reject' | 'ignore'
|
|
35
|
+
tracks: [
|
|
36
|
+
{
|
|
37
|
+
id: 'beat',
|
|
38
|
+
kind: 'cue',
|
|
39
|
+
steps: [
|
|
40
|
+
{
|
|
41
|
+
id: 'line-1',
|
|
42
|
+
timing: { kind: 'absolute', atFrame: 0 },
|
|
43
|
+
durationFrames: 6,
|
|
44
|
+
effect: { kind: 'cue', name: 'line-1' },
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'audio',
|
|
50
|
+
kind: 'audio',
|
|
51
|
+
steps: [
|
|
52
|
+
{
|
|
53
|
+
id: 'line-1-audio',
|
|
54
|
+
timing: { kind: 'simultaneous', withStepId: 'line-1', order: 0 },
|
|
55
|
+
durationFrames: 6,
|
|
56
|
+
effect: { kind: 'audio', assetBinding: 'line' },
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
fallbacks: [
|
|
62
|
+
{
|
|
63
|
+
id: 'text-only',
|
|
64
|
+
when: { capability: 'audio', status: 'failed' },
|
|
65
|
+
omitTrackIds: ['audio'],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Step timing: `absolute` (`atFrame`), `relative` (`afterStepId`, `delayFrames`), or `simultaneous` (`withStepId`, `order`). Completion is `duration` (default) or `immediate`. Simultaneous steps at one frame run in `order`, then `trackId`, then `stepId`. On each `step`, due completions run before new activations so a `relative` follow-up with `delayFrames: 0` does not clobber the finishing step's restore.
|
|
72
|
+
|
|
73
|
+
## `playSequence(sequenceId, { invocationId, bindings })`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const player = createPresentationSequencePlayer({
|
|
77
|
+
originFrame: 0,
|
|
78
|
+
audio: headlessAudio, // or createAudioCueTimeline
|
|
79
|
+
semantic,
|
|
80
|
+
visual,
|
|
81
|
+
sequences: [defined.sequence],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
player.playSequence('spoken-line', {
|
|
85
|
+
invocationId: 'inv-1',
|
|
86
|
+
idempotencyKey: 'inv-1',
|
|
87
|
+
bindings: {
|
|
88
|
+
assets: { line: 'line-1' },
|
|
89
|
+
captions: { line: 'spoken line' },
|
|
90
|
+
regions: { speaker: 'speaker' },
|
|
91
|
+
layers: { room: 'room-bg' },
|
|
92
|
+
versions: { room: 'v1' },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
player.step(6);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The player steps bound audio and visual controllers with the same delta so one sequence owns the clock. Caption visibility lives on the player. Semantic focus is `setRegionState`. Visual dim/restore is `visual.play({ kind: 'hide' | 'show' })`. Cue tracks use `createPresentationTimeline`.
|
|
99
|
+
|
|
100
|
+
## Policies
|
|
101
|
+
|
|
102
|
+
| Policy | Behavior |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `skip()` | Restores caption / audio / focus / visual under one policy, emits `skipped`. A second skip is a no-op. The next `playSequence` is available exactly once. |
|
|
105
|
+
| `replay()` | Restarts the last invocation from playhead 0. |
|
|
106
|
+
| `pause()` / `resume()` | Freeze or continue the shared clock. `step` is ignored while paused. |
|
|
107
|
+
| `replace` (default interruption) | Incoming play interrupts the active invocation (`interrupted`) and starts the new one. |
|
|
108
|
+
| `queue` | Incoming play waits until the active invocation completes, skips, or cancels. |
|
|
109
|
+
| `reject` | Incoming play returns `{ ok: false, reason: 'busy' }`. |
|
|
110
|
+
| `ignore` | Incoming play keeps the active invocation. |
|
|
111
|
+
| `cancel()` | Aborts as `interrupted` without semantic completion. |
|
|
112
|
+
| Same `idempotencyKey` | Idempotent: no second `started`. |
|
|
113
|
+
|
|
114
|
+
## Fallbacks and reduced sensory
|
|
115
|
+
|
|
116
|
+
Readiness is probed before `started`. Failed / unavailable / unauthorized / muted audio selects the matching authored fallback (typically `omitTrackIds: ['audio']`) instead of throwing and leaving mixed channels. Semantic steps still complete.
|
|
117
|
+
|
|
118
|
+
`reducedMotion: true` shortens step duration (`skip` / `complete` → 0, or `{ durationFrames: n }`). `reducedSensory: true` omits audio tracks. Caption, focus, and completion still run.
|
|
119
|
+
|
|
120
|
+
## Snapshot / mid-line restore
|
|
121
|
+
|
|
122
|
+
`schemaVersion` is `PRESENTATION_SEQUENCE_SNAPSHOT_SCHEMA_VERSION` (`1`). Restore **fails closed** on unknown version or unknown sequence id and leaves live state unchanged. Unknown queued ids are rejected in that pre-mutation check, before live adapters are released. After mutation, unknown queue rows are skipped rather than failed: `fail()` restores JS backup but does not re-apply adapter effects.
|
|
123
|
+
|
|
124
|
+
Mid-line policy:
|
|
125
|
+
|
|
126
|
+
1. Restore the playhead (`frame`, `startedAtFrame`, completed and active step ids, selected fallback, bindings).
|
|
127
|
+
2. Re-apply currently active step effects (caption visibility, speaker focus, visual hide/show, remaining audio/cue duration).
|
|
128
|
+
3. Do not re-run completed steps.
|
|
129
|
+
4. A **skipped** invocation restores as skipped: remaining duration is not played, channels stay released.
|
|
130
|
+
5. Hosts should pass fresh or already-restored audio / semantic / visual adapters; this module does not seek those clocks backward.
|
|
131
|
+
|
|
132
|
+
## Lifecycle events (CYB-59 contracts)
|
|
133
|
+
|
|
134
|
+
`presentationSequenceEventContracts()` registers:
|
|
135
|
+
|
|
136
|
+
| `type` | When |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `presentation.sequence.state.requested` | `playSequence` accepted |
|
|
139
|
+
| `presentation.sequence.state.preloading` | Capability probe |
|
|
140
|
+
| `presentation.sequence.state.ready` | Fallback selected if needed |
|
|
141
|
+
| `presentation.sequence.state.started` | First steps may apply |
|
|
142
|
+
| `presentation.sequence.state.step-started` | Step becomes active |
|
|
143
|
+
| `presentation.sequence.state.step-completed` | Duration elapsed |
|
|
144
|
+
| `presentation.sequence.state.skipped` | `skip()` |
|
|
145
|
+
| `presentation.sequence.state.interrupted` | replace / cancel |
|
|
146
|
+
| `presentation.sequence.state.failed` | Unrecoverable (reserved) |
|
|
147
|
+
| `presentation.sequence.state.completed` | All steps terminal |
|
|
148
|
+
| `presentation.sequence.diagnostic.lifecycle` | Diagnostics |
|
|
149
|
+
|
|
150
|
+
`inspectCueIntent(sequenceId, { fallbackId })` returns the planned steps without calling the audio adapter.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Production scenario
|
|
2
|
+
|
|
3
|
+
End-to-end production composition harness. One scenario definition mounts the real host adapter, reducer, runtime group, production carts, DOM layers, compositor, visual/semantic layers, bindings, sequences, asset path, and headless audio. Isolated effect tests are not a substitute: omitting a required cart or compositor layer fails closed.
|
|
4
|
+
|
|
5
|
+
Related: [Browser harness](browser-harness.md), [Compositor](compositor.md), [Visual layers](visual-layers.md), [Semantic layers](semantic-layers.md), [Presentation sequences](presentation-sequences.md), [Presentation bindings](presentation-bindings.md), [Audio](audio.md), [Replay inspector](replay-inspector.md), [Snapshots](snapshots.md), [Runtime group](runtime-group.md), [Asset resolver](asset-resolver.md). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-82-production-scenario.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
| Surface | Import | Use |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Production host | `defineProductionScenario` / `createProductionScenarioRunner` from `@cyberart-io/engine` | Prove the shipped composition, not a second runtime. Persist `save()` envelopes in hostState. |
|
|
18
|
+
| Vitest / jsdom | same, or `@cyberart-io/engine/headless` | `installHeadlessCanvas()` first. CI and local must pass the **same** scenario definition object. |
|
|
19
|
+
|
|
20
|
+
This module composes existing harnesses. It does not reimplement Canvas2D, routing, or playback. Audio is the headless adapter: invocation and provenance only, never physical speakers.
|
|
21
|
+
|
|
22
|
+
Generic fixture ids only: `whistle`, `ripple`, `room`, `npc`.
|
|
23
|
+
|
|
24
|
+
## Contract
|
|
25
|
+
|
|
26
|
+
1. **Definition.** `schemaVersion` `1`, stable `id`, `seed`, `required.participants`, `required.layers`. JSON only. Callbacks and incomplete steps (a `click` without `selector` or `x`/`y`, a `key` without `key`) fail closed at define time. Invalid sequence or binding manifests throw when the runner is created, never silently dropped.
|
|
27
|
+
2. **Required composition.** After mount, every required participant and compositor layer must be present. Omitting the cart under test is a failure, not a skipped assertion. Missing participant localizes to `routing`; missing layer localizes to `pixels`.
|
|
28
|
+
3. **Input.** Pointer and keyboard go through the browser harness (DOM coordinates and a11y ids), then the host reducer, then the group router.
|
|
29
|
+
4. **Observation.** Evidence records reducer accept/reject, routed envelopes, selected bindings, cue sequences, composed pixels, captions, audio invocation, focus, and cleanup.
|
|
30
|
+
5. **Save / reload / replay.** `save()` writes a snapshot envelope. `reload` restores host + presentation sidecars. `replay` re-runs the inspector tape on a fresh composition and compares traces.
|
|
31
|
+
6. **Matrix.** Desktop/mobile viewport, DPR, reduced-motion, mute, failed-asset ids, and audio fallback are fields on the same definition. Not every cell needs a unique golden.
|
|
32
|
+
7. **Evidence bundle.** `ProductionScenarioEvidenceBundle` carries pixel/semantic/a11y/trace/snapshot/asset hashes, revisions, traces, screenshot metadata, and `firstBrokenBoundary` localization: input → reducer → routing → binding → cue → pixels/caption/audio → save-replay. The optional `selectionTrace` field holds a [selection trace](selection-trace.md) export (`attachSelectionTrace(bundle, exported)`); the runner never populates it itself.
|
|
33
|
+
8. **CI = local.** `run(steps)` executes the definition’s steps (or a shared step list). Do not fork scenario JSON for CI.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
createProductionScenarioRunner,
|
|
38
|
+
defineProductionScenario,
|
|
39
|
+
} from '@cyberart-io/engine';
|
|
40
|
+
import { installHeadlessCanvas } from '@cyberart-io/engine/headless';
|
|
41
|
+
|
|
42
|
+
installHeadlessCanvas();
|
|
43
|
+
|
|
44
|
+
const defined = defineProductionScenario({
|
|
45
|
+
id: 'room-whistle',
|
|
46
|
+
schemaVersion: 1,
|
|
47
|
+
seed: `0x${'82'.repeat(32)}`,
|
|
48
|
+
required: { participants: ['room', 'ripple'], layers: ['room', 'ripple'] },
|
|
49
|
+
matrix: { viewport: 'desktop', dpr: 1 },
|
|
50
|
+
steps: [
|
|
51
|
+
{ type: 'click', selector: '#whistle' },
|
|
52
|
+
{ type: 'step', frames: 2 },
|
|
53
|
+
{ type: 'save' },
|
|
54
|
+
{ type: 'replay' },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
if (!defined.ok) throw new Error(defined.errors.map((e) => e.detail).join('; '));
|
|
58
|
+
|
|
59
|
+
const runner = createProductionScenarioRunner({
|
|
60
|
+
scenario: defined.scenario,
|
|
61
|
+
participants,
|
|
62
|
+
compositorLayers,
|
|
63
|
+
visualLayers,
|
|
64
|
+
semanticRegions,
|
|
65
|
+
sequences,
|
|
66
|
+
bindings,
|
|
67
|
+
geometry,
|
|
68
|
+
host,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const evidence = await runner.run();
|
|
72
|
+
runner.destroy();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Event | When |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `production.scenario.state.observed` | Evidence captured with no broken boundary |
|
|
78
|
+
| `production.scenario.state.failed` | Required composition missing, or an expectation failed |
|
|
79
|
+
| `production.scenario.diagnostic.lifecycle` | Machine-readable rejection detail (`code`, `boundary`); emitted alongside every `state.failed` |
|
|
80
|
+
|
|
81
|
+
Contracts: `productionScenarioEventContracts()`.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Remote cart manifests
|
|
2
|
+
|
|
3
|
+
Signed, versioned cart catalogs for third-party worlds. HMAC signatures, declared asset hashes, and deny-by-default host grants. Isolation is in-process (trusted factories), not `eval` / `Function`.
|
|
4
|
+
|
|
5
|
+
Related: [capability manifest](capability-manifest.md) (optional nested `capabilities` + `grants`), [asset resolver](asset-resolver.md) (`createDeclaredAssetResolver`), [executable modules](executable-modules.md), [snapshots](snapshots.md) (`provenance`). Built on this: [content revision](content-revision.md) (`createRemoteContentAdapter` verifies the same signed JSON), [portal lifecycle](portal-lifecycle.md) (`HostGrantSet` transfer to the child cart). Back to the [package README](../README.md).
|
|
6
|
+
|
|
7
|
+
## One-command reproduce (this repo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm exec vitest run packages/engine/src/canvas/cyb-74-remote-cart-manifest.repro.spec.ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Load path
|
|
14
|
+
|
|
15
|
+
1. Parse a signed JSON envelope `{ body, signature }`.
|
|
16
|
+
2. Verify `hmac-sha256` over the canonical body (sorted keys) with a host key id.
|
|
17
|
+
3. Check `cartVersion` when the host asked for an exact version.
|
|
18
|
+
4. List `requestedGrants` (`audio`, `controller`, `network`, `persistence`, `fullscreen`).
|
|
19
|
+
5. Host applies a subset via `createHostGrantSet` (inspectable, revocable, restore fail-closed).
|
|
20
|
+
6. `createDeclaredAssetResolver` may fetch **only** declared URLs; hash mismatch or missing bytes refuse load.
|
|
21
|
+
7. Capability bag APIs throw `RemoteCartCapabilityError` (`capability-denied`) when ungranted. Granted `network.fetch` still cannot reach undeclared URLs, and returns a copy of signed bytes.
|
|
22
|
+
8. `createRemoteCartSandbox` takes the same `keys` map and re-verifies HMAC on `loadAssets`. Nested `capabilities.grants` are checked against the **host** grant set, not the cart's own list.
|
|
23
|
+
|
|
24
|
+
Tests inject a registry map or `fetch` adapter. There is no ambient network.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import {
|
|
28
|
+
createRemoteCartRegistry,
|
|
29
|
+
createRemoteCartSandbox,
|
|
30
|
+
loadRemoteCart,
|
|
31
|
+
signRemoteCartManifest,
|
|
32
|
+
} from '@cyberart-io/engine';
|
|
33
|
+
|
|
34
|
+
const signed = await signRemoteCartManifest(body, secret, 'cyb-74-test');
|
|
35
|
+
if (!signed.ok) throw new Error(signed.errors.map((e) => e.detail).join('; '));
|
|
36
|
+
|
|
37
|
+
const loaded = await loadRemoteCart({
|
|
38
|
+
source: { kind: 'registry', id: 'cyb-74.remote' },
|
|
39
|
+
keys: { 'cyb-74-test': secret },
|
|
40
|
+
registry: createRemoteCartRegistry({ 'cyb-74.remote': signed.manifest }),
|
|
41
|
+
expectedVersion: '1.0.0',
|
|
42
|
+
});
|
|
43
|
+
if (!loaded.ok) throw new Error(loaded.errors.map((e) => e.detail).join('; '));
|
|
44
|
+
|
|
45
|
+
const sandbox = createRemoteCartSandbox({
|
|
46
|
+
manifest: loaded.manifest,
|
|
47
|
+
keys: { 'cyb-74-test': secret },
|
|
48
|
+
grants: ['audio'],
|
|
49
|
+
bytesByUrl: { 'https://registry.test/cyb-74/sprite.bin': bytes },
|
|
50
|
+
});
|
|
51
|
+
sandbox.listRequestedGrants(); // before run
|
|
52
|
+
await sandbox.loadAssets(); // re-verifies HMAC; forged envelopes refuse here
|
|
53
|
+
sandbox.inspect(); // publisher, version, signature id/alg, grant set
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`snapshotProvenance()` fills envelope `provenance` (`publisher`, `version`, `signature`, `grants`) for `defineSnapshot`.
|
|
57
|
+
|
|
58
|
+
| Diagnostic `code` | When |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `invalid-json` / `invalid-manifest` | Envelope is not parseable JSON or the wrong shape. |
|
|
61
|
+
| `invalid-version` | Body version or expected `cartVersion` mismatch. |
|
|
62
|
+
| `invalid-signature` / `unknown-key` | HMAC missing, wrong, or key id unknown. |
|
|
63
|
+
| `undeclared-asset` | Resolver or granted `network.fetch` used a URL not on the manifest. |
|
|
64
|
+
| `hash-mismatch` / `unsigned-bytes` | Declared hash does not match bytes, or bytes/hash missing. |
|
|
65
|
+
| `capability-denied` | Cart called an API the host did not grant. |
|
|
66
|
+
| `destroyed` | Sandbox was torn down. |
|
package/docs/replay-inspector.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Embeddable debug API for multi-cart routing. It records router decisions, builds a causation tree, redacts configured payload keys, bounds retention, and exports a JSON tape that the headless harness can replay. There is no Player UI.
|
|
4
4
|
|
|
5
|
-
Related: [events](events.md), [runtime group](runtime-group.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md).
|
|
5
|
+
Related: [events](events.md), [runtime group](runtime-group.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md). Built on this: [selection trace](selection-trace.md) (reuses correlation ids, `DEFAULT_SENSITIVE_KEYS` / `REDACTED_VALUE`), [production scenario](production-scenario.md) (inspector records in the evidence bundle), [job orchestration](job-orchestration.md) and [world patch](world-patch.md) (redacted trace export).
|
|
6
6
|
|
|
7
7
|
Back to the [package README](../README.md).
|
|
8
8
|
|
package/docs/runtime-group.md
CHANGED
|
@@ -4,7 +4,7 @@ Host helper that mounts several production `createRuntime` carts, attaches each
|
|
|
4
4
|
|
|
5
5
|
Capability-manifest integration is optional and structural (`capability?: { emit, subscribe, authoritative }`). This module does not import the manifest.
|
|
6
6
|
|
|
7
|
-
Related: [events](events.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md), [compositor](compositor.md), [replay inspector](replay-inspector.md), [calculation carts](calculation-carts.md).
|
|
7
|
+
Related: [events](events.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md), [compositor](compositor.md), [replay inspector](replay-inspector.md), [calculation carts](calculation-carts.md). Built on this: [portal lifecycle](portal-lifecycle.md) (per-participant suspend while a child cart is entered), [production scenario](production-scenario.md) (required participants are checked against this group).
|
|
8
8
|
|
|
9
9
|
Back to the [package README](../README.md).
|
|
10
10
|
|