@cyberart-io/engine 0.0.4 → 0.0.5

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.
@@ -0,0 +1,88 @@
1
+ # Replay inspector
2
+
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
+
5
+ Related: [events](events.md), [runtime group](runtime-group.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md).
6
+
7
+ Back to the [package README](../README.md).
8
+
9
+ ## One-command reproduce (this repo)
10
+
11
+ ```bash
12
+ pnpm exec vitest run packages/engine/src/canvas/cyb-65-replay-inspector.repro.spec.ts
13
+ ```
14
+
15
+ ## When to use
16
+
17
+ | Surface | Import | Use |
18
+ |---|---|---|
19
+ | Production host | `createReplayInspector` from `@cyberart-io/engine` | Attach to `createEventRouter` or `createRuntimeGroup`. |
20
+ | Vitest / CI | same API, or from `@cyberart-io/engine/headless` | `replayExportedTrace` against `createHeadlessMultiCartHarness`. |
21
+
22
+ Hosts attach this inspector to a router or runtime group. Event types stay host-defined.
23
+
24
+ ## `createReplayInspector(options?)`
25
+
26
+ ```ts
27
+ import { createReplayInspector } from '@cyberart-io/engine';
28
+ import { createHeadlessMultiCartHarness } from '@cyberart-io/engine/headless';
29
+
30
+ const group = createHeadlessMultiCartHarness({
31
+ origin: 0,
32
+ participants: [
33
+ { id: 'effects', cart: effectsCart, emit: ['overlay.intent.*'], subscribe: ['host.state.*'] },
34
+ { id: 'ambience', cart: ambienceCart, subscribe: ['overlay.intent.*'] },
35
+ ],
36
+ });
37
+
38
+ const inspector = createReplayInspector({
39
+ redactedKeys: ['token'],
40
+ maxRecords: 512,
41
+ });
42
+ const session = inspector.bind(group);
43
+ session.publish({ type: 'host.state.accepted', kind: 'state', payload: { token: 'secret' } });
44
+ await session.step(2);
45
+ const exported = await session.exportTrace();
46
+ inspector.destroy();
47
+ group.destroy();
48
+ ```
49
+
50
+ `host.state.accepted` delivered to `effects` causes `overlay.intent.play` for `ambience`. The inspector tree is that chain plus any later rejects.
51
+
52
+ ### Options (`CreateReplayInspectorOptions`)
53
+
54
+ | Option | Default | Meaning |
55
+ |---|---|---|
56
+ | `redactedKeys` | `[]` | Object keys replaced with `REDACTED_VALUE` in payloads and cart state summaries. |
57
+ | `maxRecords` | `512` | Oldest records drop; `report().dropped` counts them. |
58
+ | `registry` | none | `ContractRegistry.get` annotates `payloadSchemaVersion`. |
59
+
60
+ ### Bind vs watch
61
+
62
+ | Method | Meaning |
63
+ |---|---|
64
+ | `watchRouter(router)` | Copy existing `inspectDecisions()`, then subscribe. |
65
+ | `bind(group)` | Watch the group router and record a `publish` / `dispatch` / `step` / asset tape. `publish` extras (`cause`) are stored and replayed. `unbind()` drops the decision subscription. |
66
+ | `importTrace(json)` | Load an export for inspection without replaying. |
67
+ | `exportTrace(filter?)` | JSON: participants, tape, redacted records, snapshots. |
68
+ | `report(filter?)` | Machine-readable CI report: participants, records, trees, dropped. |
69
+ | `causationTree(correlationId?)` | Forest of accepted/rejected nodes. Duplicates are omitted. |
70
+ | `replayExportedTrace(export, group)` | Play the tape on a fresh group; compare with `compareReplayTraces`. |
71
+
72
+ `dispatch` of `cyberart.asset.ready` / `cyberart.asset.failed` is stored as tape `kind: 'asset'`.
73
+
74
+ Cart summaries include `emit` / `subscribe` / `authoritative`, clock, redacted state, and `errorCount` / `lastError` (message only).
75
+
76
+ ## Router traces
77
+
78
+ `createEventRouter` now also exposes:
79
+
80
+ | Method | Meaning |
81
+ |---|---|
82
+ | `inspectParticipants()` | id, emit, subscribe, authoritative. |
83
+ | `inspectDecisions()` | Bounded decision log (`maxDecisions`, default 256). |
84
+ | `subscribeDecision(listener)` | Live accepted / rejected / duplicate records. |
85
+
86
+ `RuntimeGroup.inspectParticipants()` adds `kind`. Group `inspect()` includes the same attach fields. `maxTrace` bounds the accepted-event envelope log (default 1024).
87
+
88
+ Duplicate idempotency hits are `outcome: 'duplicate'` / `reason: 'duplicate'` and do not fan out. Permission failures stay `unauthorized`; missing targets `unknown-target`; spent TTL `hop-limit`.
@@ -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).
7
+ Related: [events](events.md), [headless harness](headless-harness.md), [deterministic mode](deterministic-mode.md), [compositor](compositor.md), [replay inspector](replay-inspector.md).
8
8
 
9
9
  Back to the [package README](../README.md).
10
10
 
@@ -40,7 +40,7 @@ const group = createRuntimeGroup({
40
40
  cart: effectsCart,
41
41
  seed: `0x${'61'.repeat(32)}`,
42
42
  emit: ['ambience.intent.*'],
43
- subscribe: ['adventure.state.*'],
43
+ subscribe: ['host.state.*'],
44
44
  },
45
45
  {
46
46
  id: 'ambience',
@@ -53,16 +53,16 @@ const group = createRuntimeGroup({
53
53
  });
54
54
 
55
55
  group.publish({
56
- type: 'adventure.state.loon-whistle',
56
+ type: 'host.state.accepted',
57
57
  kind: 'state',
58
- payload: { habitat: 'pond' },
58
+ payload: { id: 'north' },
59
59
  });
60
60
  await group.step(2);
61
61
  const { participants, trace, diagnostics } = await group.inspect();
62
62
  group.destroy();
63
63
  ```
64
64
 
65
- Adventure-style mapping (fixture, not Adventure Kit): an accepted `adventure.state.loon-whistle` is consumed by the effects cart, which emits `ambience.intent.play`; the router delivers that cue to the ambience cart. No project-specific bridge.
65
+ Example mapping: an accepted `host.state.accepted` is consumed by the effects cart, which emits `ambience.intent.play`; the router delivers that cue to the ambience cart. No project-specific bridge.
66
66
 
67
67
  ### Options (`CreateRuntimeGroupOptions`)
68
68
 
@@ -74,6 +74,7 @@ Adventure-style mapping (fixture, not Adventure Kit): an accepted `adventure.sta
74
74
  | `createId` / `now` | `evt-1`… / first cart clock | Injected into the **one** shared router. |
75
75
  | `validate` | none | Router `validate` for cart-originated events. |
76
76
  | `router` | `{}` | Extra `createEventRouter` options (`maxHops`, `maxPerTurn`, …). |
77
+ | `maxTrace` | `1024` | Bound on the accepted-event inspect trace. |
77
78
 
78
79
  ### Participant config
79
80
 
@@ -98,10 +99,11 @@ Adventure-style mapping (fixture, not Adventure Kit): an accepted `adventure.sta
98
99
  | `reset` | Remounts every cart to its initial state, clears traces / logs, `router.turn()`. Does not change `paused`; call `resume()` if you need lockstep after a paused reset. |
99
100
  | `dispatch(id, event)` | Mailbox inbound on that participant. |
100
101
  | `publish(event)` | Host `router.publish`. Unknown `target` → `unknown-target` rejection. |
101
- | `inspect()` | `{ participants: Record<id, { state, events, errors, kind, clock }>, trace, diagnostics }`. |
102
+ | `inspect()` | `{ participants: Record<id, { state, events, errors, kind, clock, emit, subscribe, authoritative }>, trace, diagnostics }`. |
103
+ | `inspectParticipants()` | Sync attach view (`id`, `kind`, `emit`, `subscribe`, `authoritative`). |
102
104
  | `destroy()` | Detach router listeners, `runtime.destroy()` each cart, remove owned containers. Idempotent. |
103
105
 
104
- Idempotency, correlation, causation, unauthorized emit, hop/loop checks are the existing router. The group only attaches and locksteps.
106
+ Idempotency, correlation, causation, unauthorized emit, hop/loop checks are the existing router. The group only attaches and locksteps. For “why did this effect not fire?”, attach [`createReplayInspector`](replay-inspector.md).
105
107
 
106
108
  ## `createHeadlessMultiCartHarness(options)`
107
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberart-io/engine",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "CyberArt host engine: mount carts, events, capability manifests, geometry, runtime groups, and a Node/jsdom headless entry.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",