@nodetool-ai/reliability-harness 0.8.1

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.
Files changed (167) hide show
  1. package/README.md +34 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/cli.d.ts +73 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +201 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/compare.d.ts +98 -0
  8. package/dist/compare.d.ts.map +1 -0
  9. package/dist/compare.js +267 -0
  10. package/dist/compare.js.map +1 -0
  11. package/dist/core/diff.d.ts +39 -0
  12. package/dist/core/diff.d.ts.map +1 -0
  13. package/dist/core/diff.js +114 -0
  14. package/dist/core/diff.js.map +1 -0
  15. package/dist/core/golden.d.ts +50 -0
  16. package/dist/core/golden.d.ts.map +1 -0
  17. package/dist/core/golden.js +150 -0
  18. package/dist/core/golden.js.map +1 -0
  19. package/dist/core/invariants/index.d.ts +32 -0
  20. package/dist/core/invariants/index.d.ts.map +1 -0
  21. package/dist/core/invariants/index.js +44 -0
  22. package/dist/core/invariants/index.js.map +1 -0
  23. package/dist/core/invariants/leaks.d.ts +22 -0
  24. package/dist/core/invariants/leaks.d.ts.map +1 -0
  25. package/dist/core/invariants/leaks.js +55 -0
  26. package/dist/core/invariants/leaks.js.map +1 -0
  27. package/dist/core/invariants/lifecycle.d.ts +19 -0
  28. package/dist/core/invariants/lifecycle.d.ts.map +1 -0
  29. package/dist/core/invariants/lifecycle.js +95 -0
  30. package/dist/core/invariants/lifecycle.js.map +1 -0
  31. package/dist/core/invariants/protocol-validity.d.ts +4 -0
  32. package/dist/core/invariants/protocol-validity.d.ts.map +1 -0
  33. package/dist/core/invariants/protocol-validity.js +131 -0
  34. package/dist/core/invariants/protocol-validity.js.map +1 -0
  35. package/dist/core/invariants/state-machine.d.ts +29 -0
  36. package/dist/core/invariants/state-machine.d.ts.map +1 -0
  37. package/dist/core/invariants/state-machine.js +79 -0
  38. package/dist/core/invariants/state-machine.js.map +1 -0
  39. package/dist/core/invariants/status-sets.d.ts +27 -0
  40. package/dist/core/invariants/status-sets.d.ts.map +1 -0
  41. package/dist/core/invariants/status-sets.js +51 -0
  42. package/dist/core/invariants/status-sets.js.map +1 -0
  43. package/dist/core/invariants/terminal-uniqueness.d.ts +23 -0
  44. package/dist/core/invariants/terminal-uniqueness.d.ts.map +1 -0
  45. package/dist/core/invariants/terminal-uniqueness.js +99 -0
  46. package/dist/core/invariants/terminal-uniqueness.js.map +1 -0
  47. package/dist/core/invariants/types.d.ts +25 -0
  48. package/dist/core/invariants/types.d.ts.map +1 -0
  49. package/dist/core/invariants/types.js +2 -0
  50. package/dist/core/invariants/types.js.map +1 -0
  51. package/dist/core/journey.d.ts +121 -0
  52. package/dist/core/journey.d.ts.map +1 -0
  53. package/dist/core/journey.js +166 -0
  54. package/dist/core/journey.js.map +1 -0
  55. package/dist/core/normalize.d.ts +86 -0
  56. package/dist/core/normalize.d.ts.map +1 -0
  57. package/dist/core/normalize.js +182 -0
  58. package/dist/core/normalize.js.map +1 -0
  59. package/dist/core/record.d.ts +160 -0
  60. package/dist/core/record.d.ts.map +1 -0
  61. package/dist/core/record.js +74 -0
  62. package/dist/core/record.js.map +1 -0
  63. package/dist/core/stable-json.d.ts +13 -0
  64. package/dist/core/stable-json.d.ts.map +1 -0
  65. package/dist/core/stable-json.js +28 -0
  66. package/dist/core/stable-json.js.map +1 -0
  67. package/dist/drivers/anchors.d.ts +29 -0
  68. package/dist/drivers/anchors.d.ts.map +1 -0
  69. package/dist/drivers/anchors.js +48 -0
  70. package/dist/drivers/anchors.js.map +1 -0
  71. package/dist/drivers/app-headless.d.ts +39 -0
  72. package/dist/drivers/app-headless.d.ts.map +1 -0
  73. package/dist/drivers/app-headless.js +123 -0
  74. package/dist/drivers/app-headless.js.map +1 -0
  75. package/dist/drivers/browser.d.ts +34 -0
  76. package/dist/drivers/browser.d.ts.map +1 -0
  77. package/dist/drivers/browser.js +176 -0
  78. package/dist/drivers/browser.js.map +1 -0
  79. package/dist/drivers/cli.d.ts +13 -0
  80. package/dist/drivers/cli.d.ts.map +1 -0
  81. package/dist/drivers/cli.js +82 -0
  82. package/dist/drivers/cli.js.map +1 -0
  83. package/dist/drivers/fixture-nodes.d.ts +124 -0
  84. package/dist/drivers/fixture-nodes.d.ts.map +1 -0
  85. package/dist/drivers/fixture-nodes.js +189 -0
  86. package/dist/drivers/fixture-nodes.js.map +1 -0
  87. package/dist/drivers/index.d.ts +18 -0
  88. package/dist/drivers/index.d.ts.map +1 -0
  89. package/dist/drivers/index.js +12 -0
  90. package/dist/drivers/index.js.map +1 -0
  91. package/dist/drivers/kernel.d.ts +8 -0
  92. package/dist/drivers/kernel.d.ts.map +1 -0
  93. package/dist/drivers/kernel.js +247 -0
  94. package/dist/drivers/kernel.js.map +1 -0
  95. package/dist/drivers/packaged.d.ts +28 -0
  96. package/dist/drivers/packaged.d.ts.map +1 -0
  97. package/dist/drivers/packaged.js +303 -0
  98. package/dist/drivers/packaged.js.map +1 -0
  99. package/dist/drivers/python-bridge.d.ts +22 -0
  100. package/dist/drivers/python-bridge.d.ts.map +1 -0
  101. package/dist/drivers/python-bridge.js +48 -0
  102. package/dist/drivers/python-bridge.js.map +1 -0
  103. package/dist/drivers/registry.d.ts +11 -0
  104. package/dist/drivers/registry.d.ts.map +1 -0
  105. package/dist/drivers/registry.js +18 -0
  106. package/dist/drivers/registry.js.map +1 -0
  107. package/dist/drivers/relay-fields.d.ts +70 -0
  108. package/dist/drivers/relay-fields.d.ts.map +1 -0
  109. package/dist/drivers/relay-fields.js +104 -0
  110. package/dist/drivers/relay-fields.js.map +1 -0
  111. package/dist/drivers/repo-root.d.ts +9 -0
  112. package/dist/drivers/repo-root.d.ts.map +1 -0
  113. package/dist/drivers/repo-root.js +39 -0
  114. package/dist/drivers/repo-root.js.map +1 -0
  115. package/dist/drivers/spawn-cli.d.ts +8 -0
  116. package/dist/drivers/spawn-cli.d.ts.map +1 -0
  117. package/dist/drivers/spawn-cli.js +41 -0
  118. package/dist/drivers/spawn-cli.js.map +1 -0
  119. package/dist/drivers/types.d.ts +18 -0
  120. package/dist/drivers/types.d.ts.map +1 -0
  121. package/dist/drivers/types.js +2 -0
  122. package/dist/drivers/types.js.map +1 -0
  123. package/dist/drivers/ws-server.d.ts +8 -0
  124. package/dist/drivers/ws-server.d.ts.map +1 -0
  125. package/dist/drivers/ws-server.js +382 -0
  126. package/dist/drivers/ws-server.js.map +1 -0
  127. package/dist/faults/bridge-faults.d.ts +7 -0
  128. package/dist/faults/bridge-faults.d.ts.map +1 -0
  129. package/dist/faults/bridge-faults.js +90 -0
  130. package/dist/faults/bridge-faults.js.map +1 -0
  131. package/dist/faults/host-faults.d.ts +40 -0
  132. package/dist/faults/host-faults.d.ts.map +1 -0
  133. package/dist/faults/host-faults.js +70 -0
  134. package/dist/faults/host-faults.js.map +1 -0
  135. package/dist/faults/index.d.ts +21 -0
  136. package/dist/faults/index.d.ts.map +1 -0
  137. package/dist/faults/index.js +29 -0
  138. package/dist/faults/index.js.map +1 -0
  139. package/dist/faults/net-proxy.d.ts +75 -0
  140. package/dist/faults/net-proxy.d.ts.map +1 -0
  141. package/dist/faults/net-proxy.js +305 -0
  142. package/dist/faults/net-proxy.js.map +1 -0
  143. package/dist/faults/provider-faults.d.ts +12 -0
  144. package/dist/faults/provider-faults.d.ts.map +1 -0
  145. package/dist/faults/provider-faults.js +110 -0
  146. package/dist/faults/provider-faults.js.map +1 -0
  147. package/dist/faults/registry.d.ts +17 -0
  148. package/dist/faults/registry.d.ts.map +1 -0
  149. package/dist/faults/registry.js +15 -0
  150. package/dist/faults/registry.js.map +1 -0
  151. package/dist/faults/types.d.ts +42 -0
  152. package/dist/faults/types.d.ts.map +1 -0
  153. package/dist/faults/types.js +2 -0
  154. package/dist/faults/types.js.map +1 -0
  155. package/dist/faults/ws-faults.d.ts +6 -0
  156. package/dist/faults/ws-faults.d.ts.map +1 -0
  157. package/dist/faults/ws-faults.js +48 -0
  158. package/dist/faults/ws-faults.js.map +1 -0
  159. package/dist/faults/ws-proxy.d.ts +36 -0
  160. package/dist/faults/ws-proxy.d.ts.map +1 -0
  161. package/dist/faults/ws-proxy.js +40 -0
  162. package/dist/faults/ws-proxy.js.map +1 -0
  163. package/dist/index.d.ts +12 -0
  164. package/dist/index.d.ts.map +1 -0
  165. package/dist/index.js +12 -0
  166. package/dist/index.js.map +1 -0
  167. package/package.json +75 -0
@@ -0,0 +1,182 @@
1
+ /** Field names (anywhere in a message, at any depth) treated as ids that get
2
+ * remapped to a stable `<class:N>` placeholder, keyed by their remap class. */
3
+ export const ID_FIELD_CLASSES = {
4
+ job_id: "job",
5
+ workflow_id: "workflow",
6
+ node_id: "node",
7
+ edge_id: "edge",
8
+ provider_request_id: "provider-request"
9
+ };
10
+ /** Field names replaced wholesale with `<ts>` — the value never matters, only presence. */
11
+ export const TIMESTAMP_FIELDS = [
12
+ "timestamp",
13
+ "ts",
14
+ "created_at",
15
+ "generatedAt"
16
+ ];
17
+ /** Field names replaced wholesale with `<duration>`. */
18
+ export const DURATION_FIELDS = [
19
+ "duration",
20
+ "durationMs",
21
+ "duration_ms"
22
+ ];
23
+ /**
24
+ * Field values treated as interchangeable "nothing here" signals (added by
25
+ * C2): a node with zero declared `@prop` fields (the harness's own fixture
26
+ * nodes in `drivers/fixture-nodes.ts` — no shipped node has none) serializes
27
+ * its `properties` as `null` on the raw kernel stream but as `{}` once
28
+ * relayed through the ws-server. Canonicalizing `null` to `{}` for these
29
+ * keys make the two the same "no properties" value.
30
+ */
31
+ export const NULLABLE_OBJECT_FIELDS = ["properties"];
32
+ /**
33
+ * Top-level fields the WebSocket transport stamps on a frame so a dropped
34
+ * client can replay what it missed (`job-run-registry.ts`,
35
+ * `chat-turn-registry.ts`). They describe the delivery, not the run, and the
36
+ * raw kernel stream has no counterpart — dropped from the comparison view so
37
+ * a resumable surface still diffs clean against a non-resumable one.
38
+ */
39
+ export const TRANSPORT_ONLY_FIELDS = [
40
+ "job_seq",
41
+ "chat_seq"
42
+ ];
43
+ /**
44
+ * Node type prefixes whose `node_update`/`generation_complete` frames are
45
+ * dropped from comparison (added by C2). This mirrors a real, documented,
46
+ * intentional ws-server behavior (`websocket-client-session.ts`: "Skip
47
+ * messages for constant/input nodes — they produce trivial outputs that
48
+ * don't need to be relayed to the frontend") that the raw kernel stream has
49
+ * no equivalent for — the kernel emits every node's lifecycle unconditionally.
50
+ * Normalizing both sides down to what a real frontend actually receives is
51
+ * the honest comparison; dropping the frames here (not filtering in
52
+ * `diff.ts`) keeps the diff itself surface-agnostic.
53
+ */
54
+ export const TRIVIAL_NODE_TYPE_PREFIXES = [
55
+ "nodetool.constant.",
56
+ "nodetool.input."
57
+ ];
58
+ const TRIVIAL_FRAME_MESSAGE_TYPES = new Set([
59
+ "node_update",
60
+ "generation_complete"
61
+ ]);
62
+ /** True when `frame` is one `TRIVIAL_NODE_TYPE_PREFIXES` says to drop. */
63
+ function isTrivialNodeFrame(message) {
64
+ if (!TRIVIAL_FRAME_MESSAGE_TYPES.has(String(message["type"])))
65
+ return false;
66
+ const nodeType = message["node_type"];
67
+ return (typeof nodeType === "string" &&
68
+ TRIVIAL_NODE_TYPE_PREFIXES.some((prefix) => nodeType.startsWith(prefix)));
69
+ }
70
+ /** Asset references: `asset://…`, an `/assets/…` URL, or an inline data URI. */
71
+ const ASSET_URL_PATTERN = /^(asset:\/\/|https?:\/\/[^\s]+\/assets\/|data:[^;]+;base64,)/;
72
+ /**
73
+ * Assigns each distinct original id value a stable, order-of-first-sight
74
+ * placeholder within one normalization pass. Two records normalized
75
+ * independently line up as long as ids first appear in the same relative
76
+ * order — which is exactly the "same journey, different surface" case this
77
+ * exists for.
78
+ */
79
+ export class IdMapper {
80
+ maps = new Map();
81
+ placeholderFor(cls, value) {
82
+ let map = this.maps.get(cls);
83
+ if (!map) {
84
+ map = new Map();
85
+ this.maps.set(cls, map);
86
+ }
87
+ let placeholder = map.get(value);
88
+ if (!placeholder) {
89
+ placeholder = `<${cls}:${map.size}>`;
90
+ map.set(value, placeholder);
91
+ }
92
+ return placeholder;
93
+ }
94
+ }
95
+ function normalizeValue(value, key, mapper) {
96
+ if (key && key in ID_FIELD_CLASSES && typeof value === "string") {
97
+ return mapper.placeholderFor(ID_FIELD_CLASSES[key], value);
98
+ }
99
+ if (key && TIMESTAMP_FIELDS.includes(key)) {
100
+ return value == null ? value : "<ts>";
101
+ }
102
+ if (key && DURATION_FIELDS.includes(key)) {
103
+ return value == null ? value : "<duration>";
104
+ }
105
+ if (key && NULLABLE_OBJECT_FIELDS.includes(key) && value === null) {
106
+ return {};
107
+ }
108
+ if (typeof value === "string" && ASSET_URL_PATTERN.test(value)) {
109
+ return "<asset>";
110
+ }
111
+ if (Array.isArray(value)) {
112
+ return value.map((item) => normalizeValue(item, null, mapper));
113
+ }
114
+ if (value !== null && typeof value === "object") {
115
+ const out = {};
116
+ for (const [k, v] of Object.entries(value)) {
117
+ out[k] = normalizeValue(v, k, mapper);
118
+ }
119
+ return out;
120
+ }
121
+ return value;
122
+ }
123
+ /**
124
+ * `job_update`'s optional `result` field (added by C2): the ws-server relay
125
+ * appends an "authoritative terminal snapshot" `job_update` carrying the
126
+ * full `result.outputs` whenever the relayed kernel terminal frame lacked
127
+ * one (`websocket-client-session.ts`'s `terminalWithResultSeen` check) — the
128
+ * kernel's own `job_update` emissions (`runner.ts`) never set `result` at
129
+ * all. Dropped only on `job_update` (not `node_update`, whose own `result`
130
+ * is real per-node data the outputs assertion depends on).
131
+ */
132
+ function stripJobUpdateResult(message) {
133
+ if (message["type"] !== "job_update" || !("result" in message))
134
+ return message;
135
+ const rest = { ...message };
136
+ delete rest["result"];
137
+ return rest;
138
+ }
139
+ /** Normalizes one message (a frame's payload) in place-equivalent (returns a new object). */
140
+ export function normalizeMessage(message, mapper) {
141
+ const payload = { ...stripJobUpdateResult(message) };
142
+ for (const field of TRANSPORT_ONLY_FIELDS)
143
+ delete payload[field];
144
+ return normalizeValue(payload, null, mapper);
145
+ }
146
+ /**
147
+ * Normalizes a whole `RunRecord`: every frame's message, plus the record's
148
+ * own `jobId`/`workflowId` (through the same mapper, so they match the
149
+ * placeholders used inside the frames). `startedAt`/`finishedAt`/`durationMs`
150
+ * are dropped — wall-clock fields carry no comparison value once
151
+ * normalized, per §4.
152
+ */
153
+ export function normalizeRunRecord(record, mapper = new IdMapper()) {
154
+ const frames = record.frames
155
+ // Frames a driver tagged as its surface's own documented redundancy
156
+ // (`RunFrame.redundant`) have no counterpart on the kernel's raw stream,
157
+ // so they are dropped here — in the comparison view only. The record
158
+ // itself keeps them, and the invariants read the record.
159
+ .filter((frame) => frame.redundant === undefined)
160
+ .filter((frame) => !isTrivialNodeFrame(frame.message))
161
+ .map((frame) => ({
162
+ seq: frame.seq,
163
+ channel: frame.channel,
164
+ direction: frame.direction,
165
+ surface: frame.surface,
166
+ message: normalizeMessage(frame.message, mapper)
167
+ }));
168
+ return {
169
+ journeyId: record.journeyId,
170
+ surface: record.surface,
171
+ status: record.status,
172
+ error: record.error,
173
+ jobId: record.jobId !== null
174
+ ? mapper.placeholderFor(ID_FIELD_CLASSES.job_id, record.jobId)
175
+ : null,
176
+ workflowId: record.workflowId !== null
177
+ ? mapper.placeholderFor(ID_FIELD_CLASSES.workflow_id, record.workflowId)
178
+ : null,
179
+ frames
180
+ };
181
+ }
182
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/core/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * `RunRecord`: the universal currency of the reliability harness (§12).
3
+ *
4
+ * Every driver — kernel, ws-server, cli, browser, app-headless, packaged,
5
+ * electron — produces the same shape: every frame, both directions,
6
+ * timestamped, surface-tagged. It is a superset of two existing shapes:
7
+ *
8
+ * - the debug harness's bundle (`packages/cli/src/debug/`: `report.json` +
9
+ * `server/messages.jsonl`) — one direction (server → client), no per-frame
10
+ * timestamps, one `ProcessingMessage` per JSONL line;
11
+ * - the e2e_runner's `record.json` (`web/src/e2e_runner/types.ts`'s
12
+ * `RunRecord`) — one direction, a flat `events: WsEvent[]` array plus
13
+ * distilled `outputs`/`logs`/`nodeIO`.
14
+ *
15
+ * Invariants and diffs (§6, `diff.ts`) are pure functions over this shape,
16
+ * so they run identically whether the record came from a live driver run or
17
+ * from a user's field debug bundle.
18
+ */
19
+ import type { ProcessingMessage } from "@nodetool-ai/protocol";
20
+ /** Which side of the wire a frame crossed. Debug bundles and today's
21
+ * e2e_runner records only ever carry `server_to_client`; a real ws-server
22
+ * driver also records `client_to_server` control frames (run/cancel/
23
+ * stream_input), which is what makes the record a strict superset. */
24
+ export type FrameDirection = "server_to_client" | "client_to_server";
25
+ /** A single frame in a run's stream, normalized to one shared shape. */
26
+ export interface RunFrame {
27
+ /** 0-based arrival order within this record — the tiebreaker when two
28
+ * frames share a timestamp, and the only ordering source for records
29
+ * whose origin (e.g. a JSONL bundle) never carried per-frame timestamps. */
30
+ seq: number;
31
+ /** Capture time, epoch ms. Synthetic (derived from `seq`) when the source
32
+ * record didn't preserve a real one — see `tsSource`. */
33
+ ts: number;
34
+ /** Whether `ts` is a real capture time or a synthetic ordering stand-in. */
35
+ tsSource: "captured" | "synthetic";
36
+ direction: FrameDirection;
37
+ /** Driver/converter that produced this frame (e.g. "kernel", "ws-server", "debug-bundle"). */
38
+ surface: string;
39
+ /** Per-node-channel diff key: `node:<id>` when the message names a node,
40
+ * `job` for job-level frames, `control` otherwise (§4's "per-node-channel,
41
+ * not globally" comparison). */
42
+ channel: string;
43
+ /** The raw frame payload. A `ProcessingMessage` for server→client frames;
44
+ * an untyped record for client→server control frames (run/cancel/…). */
45
+ message: ProcessingMessage | Record<string, unknown>;
46
+ /**
47
+ * Set when a driver recognizes this frame as its own surface's *documented*
48
+ * redundancy — a repeat of information an earlier frame already carried,
49
+ * emitted for client convenience (the ws-server's eager "running" ack and
50
+ * its authoritative terminal snapshot; see `drivers/ws-server.ts`). The
51
+ * value is the reason tag.
52
+ *
53
+ * The frame is still recorded: dropping repeats outright, as this driver
54
+ * used to, makes "exactly one terminal job_update" unfalsifiable on the one
55
+ * surface that has a wire. Cross-surface normalization skips tagged frames
56
+ * (they have no kernel counterpart), and the invariants that assert
57
+ * uniqueness discount them — so an *undocumented* duplicate, which is never
58
+ * tagged, still fails both.
59
+ */
60
+ redundant?: string;
61
+ }
62
+ /**
63
+ * Point-in-time snapshot of the leak-accounting counters §6's "Cleanup and
64
+ * leaks" section names: zero live actors, zero pending control responses,
65
+ * zero timers beyond the trigger timeout, zero pending Python-bridge
66
+ * requests, and WS slot accounting (`activeJobs`/`startingJobs`) back to
67
+ * empty. `at` distinguishes a baseline snapshot (taken before the run, so a
68
+ * driver can express "returned to its starting count" rather than
69
+ * "reached exactly zero" for a long-lived process) from the post-run one
70
+ * `leaks.ts` actually asserts against.
71
+ */
72
+ export interface ResourceCounterSnapshot {
73
+ at: "before" | "after";
74
+ liveActors: number;
75
+ pendingControlResponses: number;
76
+ pendingTimers: number;
77
+ pythonBridgePendingRequests: number;
78
+ activeJobs: number;
79
+ startingJobs: number;
80
+ }
81
+ /**
82
+ * One recorded client `WebSocketManager` state transition (§6 "State
83
+ * transitions", `WebSocketManager.ts:95`'s `STATE_TRANSITIONS`). `action` and
84
+ * `from`/`to` mirror that table's shape so `state-machine.ts` can replay the
85
+ * sequence against the declared machine.
86
+ */
87
+ export interface RecordedTransition {
88
+ /** Ordering key — a frame `seq` this transition is anchored to, or a plain
89
+ * incrementing index when the driver has no frame to anchor on. */
90
+ at: number;
91
+ action: string;
92
+ from: string;
93
+ to: string;
94
+ }
95
+ export interface RunRecord {
96
+ /** Journey this record was captured for, when known. */
97
+ journeyId?: string;
98
+ /** The driver/surface that produced this record. */
99
+ surface: string;
100
+ jobId: string | null;
101
+ workflowId: string | null;
102
+ /** Epoch ms; null when the source never recorded a start. */
103
+ startedAt: number | null;
104
+ finishedAt: number | null;
105
+ durationMs: number | null;
106
+ /** Terminal status ("completed" | "failed" | "cancelled" | …), or "unknown". */
107
+ status: string;
108
+ error: string | null;
109
+ params: Record<string, unknown>;
110
+ frames: RunFrame[];
111
+ /**
112
+ * Leak-accounting snapshots (§6), additive over C1's shape. Absent when the
113
+ * driver that produced this record doesn't instrument these counters (e.g.
114
+ * a debug-bundle conversion) — `leaks.ts` reports no violations rather than
115
+ * treating absence itself as a leak.
116
+ */
117
+ resourceCounters?: ResourceCounterSnapshot[];
118
+ /**
119
+ * Client `WebSocketManager` transitions observed during this run (§6),
120
+ * additive over C1's shape. Absent when the driver never touches a real
121
+ * client connection (e.g. the kernel driver) — `state-machine.ts` reports
122
+ * no violations rather than treating absence itself as a violation.
123
+ */
124
+ transitions?: RecordedTransition[];
125
+ }
126
+ /** Per-node-channel diff key for a message (§4, §12). */
127
+ export declare function channelForMessage(message: ProcessingMessage | Record<string, unknown>): string;
128
+ /** Builds one `RunFrame`, filling in `channel` and a synthetic `ts` when none is given. */
129
+ export declare function makeFrame(seq: number, surface: string, direction: FrameDirection, message: ProcessingMessage | Record<string, unknown>, ts?: number): RunFrame;
130
+ /** Minimal shape the debug harness's `report.json` needs to expose for conversion. */
131
+ interface DebugBundleReportLike {
132
+ target?: {
133
+ workflowId?: string | null;
134
+ } | null;
135
+ server?: {
136
+ status?: string;
137
+ error?: string | null;
138
+ durationMs?: number | null;
139
+ } | null;
140
+ }
141
+ /**
142
+ * Converts a decoded debug-bundle `report.json` + its `server/messages.jsonl`
143
+ * lines into a `RunRecord`. Pure — the IO (reading the bundle directory off
144
+ * disk) lives in {@link loadRunRecordFromDebugBundle}, so this half is
145
+ * unit-testable without touching the filesystem.
146
+ */
147
+ export declare function runRecordFromDebugBundle(report: DebugBundleReportLike, messages: Array<ProcessingMessage | Record<string, unknown>>, options?: {
148
+ journeyId?: string;
149
+ startedAt?: number;
150
+ }): RunRecord;
151
+ /**
152
+ * Reads a debug bundle directory (`report.json` + `server/messages.jsonl`)
153
+ * off disk and converts it into a `RunRecord`. This is the "debug bundle
154
+ * round-trips into a RunRecord" half of C1's done-when.
155
+ */
156
+ export declare function loadRunRecordFromDebugBundle(bundleDir: string, options?: {
157
+ journeyId?: string;
158
+ }): Promise<RunRecord>;
159
+ export {};
160
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/core/record.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ function messageNodeId(message) {
2
+ const nodeId = message["node_id"];
3
+ return typeof nodeId === "string" && nodeId.length > 0 ? nodeId : null;
4
+ }
5
+ /** Per-node-channel diff key for a message (§4, §12). */
6
+ export function channelForMessage(message) {
7
+ const record = message;
8
+ const nodeId = messageNodeId(record);
9
+ if (nodeId)
10
+ return `node:${nodeId}`;
11
+ if (record["type"] === "job_update")
12
+ return "job";
13
+ return "control";
14
+ }
15
+ /** Builds one `RunFrame`, filling in `channel` and a synthetic `ts` when none is given. */
16
+ export function makeFrame(seq, surface, direction, message, ts) {
17
+ return {
18
+ seq,
19
+ ts: ts ?? seq,
20
+ tsSource: ts === undefined ? "synthetic" : "captured",
21
+ direction,
22
+ surface,
23
+ channel: channelForMessage(message),
24
+ message
25
+ };
26
+ }
27
+ /**
28
+ * Converts a decoded debug-bundle `report.json` + its `server/messages.jsonl`
29
+ * lines into a `RunRecord`. Pure — the IO (reading the bundle directory off
30
+ * disk) lives in {@link loadRunRecordFromDebugBundle}, so this half is
31
+ * unit-testable without touching the filesystem.
32
+ */
33
+ export function runRecordFromDebugBundle(report, messages, options = {}) {
34
+ const startedAt = options.startedAt ?? 0;
35
+ const frames = messages.map((message, index) => makeFrame(index, "debug-bundle", "server_to_client", message, startedAt + index));
36
+ const jobUpdates = frames.filter((f) => f.channel === "job");
37
+ const firstJob = jobUpdates[0]?.message;
38
+ const lastJob = jobUpdates[jobUpdates.length - 1]?.message;
39
+ return {
40
+ journeyId: options.journeyId,
41
+ surface: "debug-bundle",
42
+ jobId: firstJob?.["job_id"] ??
43
+ lastJob?.["job_id"] ??
44
+ null,
45
+ workflowId: report.target?.workflowId ?? null,
46
+ startedAt: frames.length > 0 ? startedAt : null,
47
+ finishedAt: frames.length > 0 ? startedAt + (frames.length - 1) : null,
48
+ durationMs: report.server?.durationMs ?? null,
49
+ status: report.server?.status ?? "unknown",
50
+ error: report.server?.error ?? null,
51
+ params: {},
52
+ frames
53
+ };
54
+ }
55
+ /**
56
+ * Reads a debug bundle directory (`report.json` + `server/messages.jsonl`)
57
+ * off disk and converts it into a `RunRecord`. This is the "debug bundle
58
+ * round-trips into a RunRecord" half of C1's done-when.
59
+ */
60
+ export async function loadRunRecordFromDebugBundle(bundleDir, options = {}) {
61
+ const { readFile } = await import("node:fs/promises");
62
+ const { join } = await import("node:path");
63
+ const reportRaw = await readFile(join(bundleDir, "report.json"), "utf8");
64
+ const report = JSON.parse(reportRaw);
65
+ const messagesFile = join(bundleDir, "server", "messages.jsonl");
66
+ const messagesRaw = await readFile(messagesFile, "utf8");
67
+ const messages = messagesRaw
68
+ .split("\n")
69
+ .map((line) => line.trim())
70
+ .filter((line) => line.length > 0)
71
+ .map((line) => JSON.parse(line));
72
+ return runRecordFromDebugBundle(report, messages, options);
73
+ }
74
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/core/record.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Order-independent JSON serialization, recursive at every depth — the
3
+ * canonical form both the stream diff (`diff.ts`) and the golden comparisons
4
+ * (`golden.ts`) key messages by.
5
+ *
6
+ * `JSON.stringify(value, keyArray)` cannot do this: its array form is a key
7
+ * *whitelist* applied at every nesting level, so a top-level key list erases
8
+ * every nested object's contents — `{result: {output: "A"}}` and
9
+ * `{result: {output: "B"}}` both serialize to `{"result":{}}`, and output,
10
+ * metadata, and nested error divergences compare equal.
11
+ */
12
+ export declare function stableStringify(value: unknown): string;
13
+ //# sourceMappingURL=stable-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-json.d.ts","sourceRoot":"","sources":["../../src/core/stable-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CActD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Order-independent JSON serialization, recursive at every depth — the
3
+ * canonical form both the stream diff (`diff.ts`) and the golden comparisons
4
+ * (`golden.ts`) key messages by.
5
+ *
6
+ * `JSON.stringify(value, keyArray)` cannot do this: its array form is a key
7
+ * *whitelist* applied at every nesting level, so a top-level key list erases
8
+ * every nested object's contents — `{result: {output: "A"}}` and
9
+ * `{result: {output: "B"}}` both serialize to `{"result":{}}`, and output,
10
+ * metadata, and nested error divergences compare equal.
11
+ */
12
+ export function stableStringify(value) {
13
+ if (value === undefined)
14
+ return "null";
15
+ if (value === null || typeof value !== "object") {
16
+ return JSON.stringify(value) ?? "null";
17
+ }
18
+ if (Array.isArray(value)) {
19
+ return `[${value.map(stableStringify).join(",")}]`;
20
+ }
21
+ const entries = Object.entries(value)
22
+ .filter(([, v]) => v !== undefined)
23
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
24
+ return `{${entries
25
+ .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)
26
+ .join(",")}}`;
27
+ }
28
+ //# sourceMappingURL=stable-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stable-json.js","sourceRoot":"","sources":["../../src/core/stable-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACvC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC;IACzC,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACrD,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;SAC7D,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,IAAI,OAAO;SACf,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Event-anchor matching for scripted `JourneyInteraction`s (§4: "event-
3
+ * anchored, not time-anchored"). An interaction's `at` string names a
4
+ * message to wait for before firing — never a wall-clock delay — so both
5
+ * drivers cancel/stream-input at the same logical point in the run
6
+ * regardless of how fast either surface actually executes.
7
+ *
8
+ * Supported anchor shapes (the only ones the four C2 journeys need; extend
9
+ * here, not per-driver, when a later journey needs more):
10
+ * - `"node_update:<nodeId>:<status>"` — a `node_update` for that node id
11
+ * reaching that status (e.g. `"node_update:probe:running"`).
12
+ * - `"job_update:<status>"` — a `job_update` reaching that status.
13
+ * - `"<type>"` — any message of that `type`, e.g. `"chunk"`.
14
+ */
15
+ export type AnchoredMessage = Record<string, unknown>;
16
+ export declare function matchesAnchor(message: AnchoredMessage, anchor: string): boolean;
17
+ /**
18
+ * Registers anchor waiters and resolves them as matching messages flow
19
+ * through a driver's message pump. Registration must happen synchronously
20
+ * (no `await` in between) relative to the pump starting to consume already-
21
+ * queued messages, or a fast-arriving anchor could be missed — both drivers
22
+ * follow that rule (see the comment at each `run()`'s interaction loop).
23
+ */
24
+ export declare class AnchorWaiter {
25
+ private waiters;
26
+ wait(anchor: string): Promise<void>;
27
+ notify(message: AnchoredMessage): void;
28
+ }
29
+ //# sourceMappingURL=anchors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchors.d.ts","sourceRoot":"","sources":["../../src/drivers/anchors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD,wBAAgB,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAoB/E;AAOD;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAgB;IAE/B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlC;IAED,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAOrC;CACF"}
@@ -0,0 +1,48 @@
1
+ export function matchesAnchor(message, anchor) {
2
+ const parts = anchor.split(":");
3
+ const type = parts[0];
4
+ if (message["type"] !== type)
5
+ return false;
6
+ if (type === "node_update") {
7
+ const [, nodeId, status] = parts;
8
+ if (nodeId !== undefined && message["node_id"] !== nodeId)
9
+ return false;
10
+ if (status !== undefined && message["status"] !== status)
11
+ return false;
12
+ return true;
13
+ }
14
+ if (type === "job_update") {
15
+ const [, status] = parts;
16
+ if (status !== undefined && message["status"] !== status)
17
+ return false;
18
+ return true;
19
+ }
20
+ // Bare type match (e.g. "chunk") — no further qualifiers supported yet.
21
+ return true;
22
+ }
23
+ /**
24
+ * Registers anchor waiters and resolves them as matching messages flow
25
+ * through a driver's message pump. Registration must happen synchronously
26
+ * (no `await` in between) relative to the pump starting to consume already-
27
+ * queued messages, or a fast-arriving anchor could be missed — both drivers
28
+ * follow that rule (see the comment at each `run()`'s interaction loop).
29
+ */
30
+ export class AnchorWaiter {
31
+ waiters = [];
32
+ wait(anchor) {
33
+ return new Promise((resolve) => {
34
+ this.waiters.push({ test: (m) => matchesAnchor(m, anchor), resolve });
35
+ });
36
+ }
37
+ notify(message) {
38
+ const remaining = [];
39
+ for (const waiter of this.waiters) {
40
+ if (waiter.test(message))
41
+ waiter.resolve();
42
+ else
43
+ remaining.push(waiter);
44
+ }
45
+ this.waiters = remaining;
46
+ }
47
+ }
48
+ //# sourceMappingURL=anchors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchors.js","sourceRoot":"","sources":["../../src/drivers/anchors.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,aAAa,CAAC,OAAwB,EAAE,MAAc;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAE3C,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;QACjC,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;QACxE,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;QACzB,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wEAAwE;IACxE,OAAO,IAAI,CAAC;AACd,CAAC;AAOD;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IACf,OAAO,GAAa,EAAE,CAAC;IAE/B,IAAI,CAAC,MAAc;QACjB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,OAAwB;QAC7B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;;gBACtC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ import type { ProcessingMessage } from "@nodetool-ai/protocol";
2
+ import type { Journey } from "../core/journey.js";
3
+ import { type RunRecord } from "../core/record.js";
4
+ import type { RunDriver } from "./types.js";
5
+ /** Minimal shape this driver needs from an `AppDebugReport`'s `report.json`
6
+ * (`packages/cli/src/app-debug/types.ts`). */
7
+ export interface AppDebugReportLike {
8
+ target?: {
9
+ workflowId?: string | null;
10
+ } | null;
11
+ runs?: Array<{
12
+ status?: string;
13
+ error?: string | null;
14
+ durationMs?: number | null;
15
+ messagesFile?: string;
16
+ }>;
17
+ }
18
+ /** True when a journey's workflow carries an `app_doc` the app-headless
19
+ * driver can run — pure, no IO, so `compare.ts` and tests can check
20
+ * applicability without spawning a process. */
21
+ export declare function journeyHasAppDoc(journey: Journey): boolean;
22
+ /**
23
+ * Folds a decoded `AppDebugReport` (`report.json`) plus each triggered run's
24
+ * own `server/run-N.messages.jsonl` lines (already read, in `runs[]` order)
25
+ * into one `RunRecord`. Pure — mirrors `record.ts`'s
26
+ * `runRecordFromDebugBundle` split between the pure conversion and the IO
27
+ * that reads the bundle off disk, so this half is unit-testable without a
28
+ * real app document or a spawned process.
29
+ */
30
+ export declare function runRecordFromAppDebugReport(report: AppDebugReportLike, runMessages: Array<Array<ProcessingMessage | Record<string, unknown>>>, options?: {
31
+ journeyId?: string;
32
+ startedAt?: number;
33
+ }): RunRecord;
34
+ export declare class AppHeadlessDriver implements RunDriver {
35
+ readonly name = "app-headless";
36
+ supports(journey: Journey): boolean;
37
+ run(journey: Journey): Promise<RunRecord>;
38
+ }
39
+ //# sourceMappingURL=app-headless.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-headless.d.ts","sourceRoot":"","sources":["../../src/drivers/app-headless.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAA4B,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;8CAC8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C,IAAI,CAAC,EAAE,KAAK,CAAC;QACX,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;CACJ;AAED;;+CAE+C;AAC/C,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EACtE,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACvD,SAAS,CA6BX;AAED,qBAAa,iBAAkB,YAAW,SAAS;IACjD,QAAQ,CAAC,IAAI,kBAAkB;IAE/B,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAElC;IAEK,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAiE9C;CACF"}