@termwright/probe-runtime 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Framework-neutral, fail-closed socket transport shared by zero-config
4
4
  JavaScript probes. It owns handshake validation, full-snapshot publication,
5
- revision commits, marker authentication, `get-tree`, limits and channel
5
+ revision commits, marker authentication, limits and channel
6
6
  shutdown.
7
7
 
8
8
  It intentionally does not observe framework state or write markers. Only the
@@ -16,7 +16,7 @@ Collection turns on automatically with `TERMWRIGHT_DEBUG=1`/`all` or
16
16
  `TERMWRIGHT_DEBUG_FILE`, and can be forced with `connectProbe({
17
17
  performanceMetrics: true, ... })`. Disabled sessions do not call the timer.
18
18
 
19
- The snapshot reports full snapshots, deltas, encoded semantic bytes, node and
19
+ The snapshot reports full snapshots, encoded semantic bytes, node and
20
20
  generic-node counts, failed publications, requested markers and serialization
21
21
  time, together with per-frame averages. Framework sessions also supply their
22
22
  Probe IR fact count and report publications superseded in their marker queue.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Socket } from 'node:net';
2
+ import { EvidenceProviderRegistry, FrozenEvidenceProviderRegistry } from '@termwright/evidence-provider';
2
3
  import { ProtocolId, ProtocolLimits, ProbeInfo, AdapterCapability, FrameDecoder, SemanticSnapshot } from '@termwright/protocol';
3
4
 
4
5
  /**
@@ -24,7 +25,8 @@ interface ConnectOptions {
24
25
  readonly capabilities: readonly AdapterCapability[];
25
26
  readonly adapterName: string;
26
27
  readonly adapterVersion: string;
27
- readonly protocol?: ProtocolId;
28
+ /** Isolated application provider registry. Defaults to the process facade. */
29
+ readonly evidenceProviderRegistry?: EvidenceProviderRegistry;
28
30
  /** Milliseconds for connect plus handshake. Default 1000. */
29
31
  readonly handshakeTimeoutMs?: number;
30
32
  /**
@@ -44,7 +46,6 @@ interface ConnectOptions {
44
46
  interface ProbePerformanceMetrics {
45
47
  readonly enabled: boolean;
46
48
  readonly fullSnapshots: number;
47
- readonly deltas: number;
48
49
  readonly semanticBytes: number;
49
50
  readonly semanticNodes: number;
50
51
  readonly unknownFrameworkNodes: number;
@@ -87,7 +88,7 @@ declare class ProbeChannel {
87
88
  #private;
88
89
  readonly session: ChannelSession;
89
90
  /** @internal Built by {@link connectProbe} once the handshake succeeded. */
90
- constructor(socket: Socket, session: ChannelSession, token: string, decoder: FrameDecoder, pending?: readonly unknown[], performanceMetrics?: boolean, performanceSink?: (metrics: ProbePerformanceMetrics) => void);
91
+ constructor(socket: Socket, session: ChannelSession, token: string, decoder: FrameDecoder, pending?: readonly unknown[], evidenceProviders?: FrozenEvidenceProviderRegistry, performanceMetrics?: boolean, performanceSink?: (metrics: ProbePerformanceMetrics) => void);
91
92
  get isOpen(): boolean;
92
93
  /** Revisions published so far. */
93
94
  get revision(): number;
@@ -106,6 +107,8 @@ declare class ProbeChannel {
106
107
  publish(snapshot: SemanticSnapshot, publication?: ProbePublicationMetrics): string | undefined;
107
108
  /** Report a framework publication superseded before its marker could drain. */
108
109
  recordCoalescedEvent(): void;
110
+ /** Report a typed fatal producer-contract violation and close the channel. */
111
+ fail(code: 'duplicate-semantic-key' | 'adapter-guarantee-violation' | 'internal', message: string): void;
109
112
  /** Disable the channel and release the socket. Idempotent. */
110
113
  close(): void;
111
114
  }
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  import { createConnection } from "net";
3
3
  import { appendFileSync } from "fs";
4
4
  import { performance } from "perf_hooks";
5
+ import {
6
+ freezeEvidenceProviders
7
+ } from "@termwright/evidence-provider";
5
8
  import {
6
9
  createFrameDecoder,
7
10
  encodeFrame,
@@ -15,16 +18,17 @@ var ProbeChannel = class {
15
18
  #socket;
16
19
  session;
17
20
  #token;
21
+ #evidenceProviders;
18
22
  #open = true;
19
23
  #revision = 0;
20
- #latest;
21
24
  #performance;
22
25
  #performanceSink;
23
26
  /** @internal Built by {@link connectProbe} once the handshake succeeded. */
24
- constructor(socket, session, token, decoder, pending = [], performanceMetrics = false, performanceSink) {
27
+ constructor(socket, session, token, decoder, pending = [], evidenceProviders = freezeEvidenceProviders(), performanceMetrics = false, performanceSink) {
25
28
  this.#socket = socket;
26
29
  this.session = session;
27
30
  this.#token = token;
31
+ this.#evidenceProviders = evidenceProviders;
28
32
  this.#performance = performanceMetrics ? {
29
33
  fullSnapshots: 0,
30
34
  semanticBytes: 0,
@@ -67,7 +71,6 @@ var ProbeChannel = class {
67
71
  return Object.freeze({
68
72
  enabled: false,
69
73
  fullSnapshots: 0,
70
- deltas: 0,
71
74
  semanticBytes: 0,
72
75
  semanticNodes: 0,
73
76
  unknownFrameworkNodes: 0,
@@ -88,8 +91,6 @@ var ProbeChannel = class {
88
91
  return Object.freeze({
89
92
  enabled: true,
90
93
  fullSnapshots: frames,
91
- // ProbeChannel deliberately does not advertise tree-diffs.
92
- deltas: 0,
93
94
  semanticBytes: metrics.semanticBytes,
94
95
  semanticNodes: metrics.semanticNodes,
95
96
  unknownFrameworkNodes: metrics.unknownFrameworkNodes,
@@ -99,10 +100,7 @@ var ProbeChannel = class {
99
100
  averageBytesPerFrame: average(metrics.semanticBytes, frames),
100
101
  averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),
101
102
  averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),
102
- averageSerializationMicrosecondsPerFrame: average(
103
- metrics.serializationMicroseconds,
104
- frames
105
- ),
103
+ averageSerializationMicrosecondsPerFrame: average(metrics.serializationMicroseconds, frames),
106
104
  probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),
107
105
  coalescedEvents: metrics.coalescedEvents,
108
106
  renderCorrelationRate: null,
@@ -121,8 +119,26 @@ var ProbeChannel = class {
121
119
  if (this.#performance !== null) this.#performance.droppedEvents += 1;
122
120
  return void 0;
123
121
  }
122
+ const providerEvidence = this.#evidenceProviders.collect({
123
+ sessionId: snapshot.sessionId,
124
+ revision: snapshot.revision,
125
+ columns: snapshot.columns,
126
+ rows: snapshot.rows,
127
+ resolveRecipient: (recipient) => {
128
+ const matches = "semanticId" in recipient ? snapshot.nodes.filter((node) => node.id === recipient.semanticId) : "testId" in recipient ? snapshot.nodes.filter((node) => node.testId === recipient.testId) : snapshot.nodes.filter(
129
+ (node) => node.role === recipient.role && node.name === recipient.name
130
+ );
131
+ if (matches.length !== 1) {
132
+ throw new Error(
133
+ `application evidence recipient resolved to ${matches.length} semantic nodes`
134
+ );
135
+ }
136
+ return matches[0].id;
137
+ }
138
+ });
139
+ const qualifiedSnapshot = providerEvidence.length === 0 ? snapshot : Object.freeze({ ...snapshot, providerEvidence });
124
140
  const sent = this.#send(
125
- { type: "snapshot", snapshot },
141
+ { type: "snapshot", snapshot: qualifiedSnapshot },
126
142
  this.#performance !== null
127
143
  );
128
144
  if (sent === void 0) {
@@ -131,7 +147,6 @@ var ProbeChannel = class {
131
147
  return void 0;
132
148
  }
133
149
  this.#revision = snapshot.revision;
134
- this.#latest = snapshot;
135
150
  if (this.#performance !== null) {
136
151
  this.#performance.fullSnapshots += 1;
137
152
  this.#performance.semanticBytes += sent.bytes;
@@ -171,11 +186,17 @@ var ProbeChannel = class {
171
186
  this.#emitPerformance();
172
187
  }
173
188
  }
189
+ /** Report a typed fatal producer-contract violation and close the channel. */
190
+ fail(code, message) {
191
+ if (!this.#open) return;
192
+ this.#send({ type: "error", code, message: message.slice(0, 1024) });
193
+ this.close();
194
+ }
174
195
  /** Disable the channel and release the socket. Idempotent. */
175
196
  close() {
176
197
  if (!this.#open) return;
177
198
  this.#open = false;
178
- this.#latest = void 0;
199
+ this.#evidenceProviders.close();
179
200
  this.#socket.removeAllListeners("data");
180
201
  this.#socket.destroy();
181
202
  }
@@ -190,12 +211,6 @@ var ProbeChannel = class {
190
211
  this.close();
191
212
  return;
192
213
  }
193
- if (message.type !== "get-tree") return;
194
- const held = this.#latest;
195
- const usable = held !== void 0 && (message.revision === void 0 || message.revision === held.revision);
196
- this.#send(
197
- usable ? { type: "get-tree-result", requestId: message.requestId, snapshot: held } : { type: "get-tree-result", requestId: message.requestId, error: "revision not retained" }
198
- );
199
214
  }
200
215
  #send(message, measureSerialization = false) {
201
216
  if (!this.#open) return void 0;
@@ -251,6 +266,7 @@ function filePerformanceSink(path, context) {
251
266
  }
252
267
  async function connectProbe(options) {
253
268
  const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
269
+ const evidenceProviders = freezeEvidenceProviders(options.evidenceProviderRegistry);
254
270
  return new Promise((resolve) => {
255
271
  let settled = false;
256
272
  let socket;
@@ -266,6 +282,7 @@ async function connectProbe(options) {
266
282
  settled = true;
267
283
  detach();
268
284
  socket.destroy();
285
+ evidenceProviders.close();
269
286
  resolve(null);
270
287
  };
271
288
  const accept = (session, pending) => {
@@ -273,25 +290,29 @@ async function connectProbe(options) {
273
290
  settled = true;
274
291
  detach();
275
292
  const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);
276
- resolve(new ProbeChannel(
277
- socket,
278
- session,
279
- options.token,
280
- decoder,
281
- pending,
282
- options.performanceMetrics ?? (metricsPath !== void 0 || debugMetricsEnabled(process.env)),
283
- filePerformanceSink(metricsPath, {
284
- adapter: options.adapterName,
285
- framework: options.probe.framework,
286
- sessionId: session.sessionId
287
- })
288
- ));
293
+ resolve(
294
+ new ProbeChannel(
295
+ socket,
296
+ session,
297
+ options.token,
298
+ decoder,
299
+ pending,
300
+ evidenceProviders,
301
+ options.performanceMetrics ?? (metricsPath !== void 0 || debugMetricsEnabled(process.env)),
302
+ filePerformanceSink(metricsPath, {
303
+ adapter: options.adapterName,
304
+ framework: options.probe.framework,
305
+ sessionId: session.sessionId
306
+ })
307
+ )
308
+ );
289
309
  };
290
310
  try {
291
311
  socket = createConnection(options.endpoint);
292
312
  socket.unref();
293
313
  } catch {
294
314
  clearTimeout(timer);
315
+ evidenceProviders.close();
295
316
  resolve(null);
296
317
  return;
297
318
  }
@@ -303,11 +324,12 @@ async function connectProbe(options) {
303
324
  encodeFrame(
304
325
  {
305
326
  type: "hello",
306
- protocol: options.protocol ?? PROTOCOL_ID,
327
+ protocol: PROTOCOL_ID,
307
328
  token: options.token,
308
329
  adapter: { name: options.adapterName, version: options.adapterVersion },
309
330
  capabilities: options.capabilities,
310
- probe: options.probe
331
+ probe: options.probe,
332
+ ...evidenceProviders.registrations.length === 0 ? {} : { providers: evidenceProviders.registrations }
311
333
  },
312
334
  DEFAULT_LIMITS.maxFrameBytes
313
335
  )
@@ -332,7 +354,7 @@ async function connectProbe(options) {
332
354
  return;
333
355
  }
334
356
  const ack = parsed.message;
335
- if (ack.protocol !== (options.protocol ?? PROTOCOL_ID)) {
357
+ if (ack.protocol !== PROTOCOL_ID) {
336
358
  abandon();
337
359
  return;
338
360
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Shared process-to-driver transport for zero-config JavaScript probes.\n *\n * Framework probes deliberately keep observation and marker placement in\n * their own packages. This module owns only the framework-neutral handshake,\n * full-snapshot publication and fail-closed socket lifecycle.\n */\n\nimport { createConnection, type Socket } from 'node:net';\nimport { appendFileSync } from 'node:fs';\nimport { performance } from 'node:perf_hooks';\nimport {\n createFrameDecoder,\n encodeFrame,\n encodeMarker,\n parseDriverMessage,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n type ProtocolId,\n type AdapterCapability,\n type AdapterToDriverMessage,\n type FrameDecoder,\n type ProbeInfo,\n type ProtocolLimits,\n type SemanticSnapshot,\n} from '@termwright/protocol';\n\n/** What the driver decided for this session. */\nexport interface ChannelSession {\n readonly protocol: ProtocolId;\n readonly sessionId: string;\n readonly limits: ProtocolLimits;\n readonly markerEnabled: boolean;\n}\n\n/** Connection parameters. */\nexport interface ConnectOptions {\n readonly endpoint: string;\n readonly token: string;\n readonly probe: ProbeInfo;\n readonly capabilities: readonly AdapterCapability[];\n readonly adapterName: string;\n readonly adapterVersion: string;\n readonly protocol?: ProtocolId;\n /** Milliseconds for connect plus handshake. Default 1000. */\n readonly handshakeTimeoutMs?: number;\n /**\n * Collect per-publication counters and timings.\n *\n * Defaults on when `TERMWRIGHT_DEBUG` enables driver diagnostics or an\n * adapter debug file is configured. Explicit false keeps even the timer off.\n */\n readonly performanceMetrics?: boolean;\n /**\n * Append machine-readable performance snapshots here. Defaults to\n * `TERMWRIGHT_DEBUG_FILE`; write failures never affect the application.\n */\n readonly performanceMetricsFile?: string;\n}\n\n/** Debug-only facts the shared transport can observe without guessing. */\nexport interface ProbePerformanceMetrics {\n readonly enabled: boolean;\n readonly fullSnapshots: number;\n readonly deltas: number;\n readonly semanticBytes: number;\n readonly semanticNodes: number;\n readonly unknownFrameworkNodes: number;\n readonly droppedEvents: number;\n readonly markerRequests: number;\n readonly serializationMicroseconds: number;\n readonly averageBytesPerFrame: number | null;\n readonly averageSemanticNodesPerFrame: number | null;\n readonly averageUnknownFrameworkNodesPerFrame: number | null;\n readonly averageSerializationMicrosecondsPerFrame: number | null;\n /** Present when the framework session supplies its pre-normalized fact count. */\n readonly probeEventsPerFrame: number | null;\n /** Superseded publications reported by a framework-owned marker queue. */\n readonly coalescedEvents: number;\n /** Only the framework-specific sink knows whether a returned marker drained. */\n readonly renderCorrelationRate: null;\n /** Normalization currently happens before the shared transport is called. */\n readonly parentNormalizationMicrosecondsPerFrame: null;\n}\n\ninterface MutableProbePerformanceMetrics {\n fullSnapshots: number;\n semanticBytes: number;\n semanticNodes: number;\n unknownFrameworkNodes: number;\n droppedEvents: number;\n markerRequests: number;\n serializationMicroseconds: number;\n probeEvents: number;\n framesWithProbeEvents: number;\n coalescedEvents: number;\n}\n\nexport interface ProbePublicationMetrics {\n /** Probe IR objects plus render/layout operations observed for this frame. */\n readonly probeEvents?: number;\n}\n\n/** One JSONL record written by an injected probe in debug-file mode. */\nexport interface ProbePerformanceRecord {\n readonly kind: 'termwright-probe-performance';\n readonly adapter: string;\n readonly framework: string;\n readonly sessionId: string;\n readonly metrics: ProbePerformanceMetrics;\n}\n\ninterface SendResult {\n readonly bytes: number;\n readonly serializationMicroseconds: number;\n}\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 1_000;\n\n/**\n * A connected channel.\n *\n * `publish` returns the marker rather than writing it. Only a framework probe\n * knows when its frame bytes have drained, and emitting earlier silently pairs\n * the tree with the wrong screen.\n */\nexport class ProbeChannel {\n readonly #socket: Socket;\n readonly session: ChannelSession;\n readonly #token: string;\n #open = true;\n #revision = 0;\n #latest: SemanticSnapshot | undefined;\n readonly #performance: MutableProbePerformanceMetrics | null;\n readonly #performanceSink: (() => void) | null;\n\n /** @internal Built by {@link connectProbe} once the handshake succeeded. */\n constructor(\n socket: Socket,\n session: ChannelSession,\n token: string,\n decoder: FrameDecoder,\n pending: readonly unknown[] = [],\n performanceMetrics = false,\n performanceSink?: (metrics: ProbePerformanceMetrics) => void,\n ) {\n this.#socket = socket;\n this.session = session;\n this.#token = token;\n this.#performance = performanceMetrics\n ? {\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n probeEvents: 0,\n framesWithProbeEvents: 0,\n coalescedEvents: 0,\n }\n : null;\n this.#performanceSink = performanceSink === undefined\n ? null\n : () => performanceSink(this.performanceMetrics());\n\n socket.on('data', (chunk: Buffer) => {\n if (!this.#open) return;\n try {\n for (const value of decoder.push(chunk)) this.#dispatch(value);\n } catch {\n this.close();\n }\n });\n socket.on('error', () => this.close());\n socket.on('close', () => this.close());\n\n // The ACK and the driver's first request may share one pipe/TCP chunk. The\n // handshake decoder already removed every complete frame from its buffer,\n // so values after the ACK must be handed over explicitly or they vanish.\n for (const value of pending) this.#dispatch(value);\n }\n\n get isOpen(): boolean {\n return this.#open;\n }\n\n /** Revisions published so far. */\n get revision(): number {\n return this.#revision;\n }\n\n /**\n * A stable, immutable debug snapshot. Disabled collection reports no\n * fabricated zeros for averages that were never observed.\n */\n performanceMetrics(): ProbePerformanceMetrics {\n const metrics = this.#performance;\n if (metrics === null) {\n return Object.freeze({\n enabled: false,\n fullSnapshots: 0,\n deltas: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n averageBytesPerFrame: null,\n averageSemanticNodesPerFrame: null,\n averageUnknownFrameworkNodesPerFrame: null,\n averageSerializationMicrosecondsPerFrame: null,\n probeEventsPerFrame: null,\n coalescedEvents: 0,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n const frames = metrics.fullSnapshots;\n return Object.freeze({\n enabled: true,\n fullSnapshots: frames,\n // ProbeChannel deliberately does not advertise tree-diffs.\n deltas: 0,\n semanticBytes: metrics.semanticBytes,\n semanticNodes: metrics.semanticNodes,\n unknownFrameworkNodes: metrics.unknownFrameworkNodes,\n droppedEvents: metrics.droppedEvents,\n markerRequests: metrics.markerRequests,\n serializationMicroseconds: metrics.serializationMicroseconds,\n averageBytesPerFrame: average(metrics.semanticBytes, frames),\n averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),\n averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),\n averageSerializationMicrosecondsPerFrame: average(\n metrics.serializationMicroseconds,\n frames,\n ),\n probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),\n coalescedEvents: metrics.coalescedEvents,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n\n /**\n * Send a full tree and commit its revision.\n *\n * Full snapshots satisfy the producer side of dropped-frame recovery by\n * construction. Diff subscription can be added later without weakening this\n * initial correctness floor.\n */\n publish(\n snapshot: SemanticSnapshot,\n publication: ProbePublicationMetrics = {},\n ): string | undefined {\n if (!this.#open) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n return undefined;\n }\n const sent = this.#send(\n { type: 'snapshot', snapshot },\n this.#performance !== null,\n );\n if (sent === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n this.#revision = snapshot.revision;\n this.#latest = snapshot;\n if (this.#performance !== null) {\n this.#performance.fullSnapshots += 1;\n this.#performance.semanticBytes += sent.bytes;\n this.#performance.semanticNodes += snapshot.nodes.length;\n this.#performance.unknownFrameworkNodes += snapshot.nodes.filter(\n (node) => node.role === 'generic',\n ).length;\n this.#performance.serializationMicroseconds += sent.serializationMicroseconds;\n if (\n publication.probeEvents !== undefined\n && Number.isSafeInteger(publication.probeEvents)\n && publication.probeEvents >= 0\n ) {\n this.#performance.probeEvents += publication.probeEvents;\n this.#performance.framesWithProbeEvents += 1;\n }\n }\n if (this.#send({ type: 'revision-commit', revision: snapshot.revision }) === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n\n if (!this.session.markerEnabled) {\n this.#emitPerformance();\n return undefined;\n }\n try {\n const marker = encodeMarker(this.#token, this.session.sessionId, snapshot.revision);\n if (this.#performance !== null) this.#performance.markerRequests += 1;\n this.#emitPerformance();\n return marker;\n } catch {\n this.#emitPerformance();\n return undefined;\n }\n }\n\n /** Report a framework publication superseded before its marker could drain. */\n recordCoalescedEvent(): void {\n if (this.#performance !== null) {\n this.#performance.coalescedEvents += 1;\n this.#emitPerformance();\n }\n }\n\n /** Disable the channel and release the socket. Idempotent. */\n close(): void {\n if (!this.#open) return;\n this.#open = false;\n this.#latest = undefined;\n this.#socket.removeAllListeners('data');\n this.#socket.destroy();\n }\n\n #dispatch(value: unknown): void {\n const parsed = parseDriverMessage(value, this.session.limits);\n if (!parsed.ok) {\n this.close();\n return;\n }\n const message = parsed.message;\n if (message.type === 'error') {\n this.close();\n return;\n }\n if (message.type !== 'get-tree') return;\n\n const held = this.#latest;\n const usable = held !== undefined\n && (message.revision === undefined || message.revision === held.revision);\n this.#send(\n usable\n ? { type: 'get-tree-result', requestId: message.requestId, snapshot: held }\n : { type: 'get-tree-result', requestId: message.requestId, error: 'revision not retained' },\n );\n }\n\n #send(message: AdapterToDriverMessage, measureSerialization = false): SendResult | undefined {\n if (!this.#open) return undefined;\n let frame: Uint8Array;\n let serializationMicroseconds: number;\n try {\n const started = measureSerialization ? performance.now() : 0;\n frame = encodeFrame(message, this.session.limits.maxFrameBytes);\n serializationMicroseconds = measureSerialization\n ? (performance.now() - started) * 1_000\n : 0;\n } catch {\n // This is locally produced data and encodeFrame writes nothing before it\n // rejects. Drop only this publication: a later, smaller framework frame\n // can still be valid and must not lose the already negotiated channel.\n return undefined;\n }\n try {\n this.#socket.write(frame);\n return { bytes: frame.byteLength, serializationMicroseconds };\n } catch {\n this.close();\n return undefined;\n }\n }\n\n #emitPerformance(): void {\n try {\n this.#performanceSink?.();\n } catch {\n // Debug output is strictly fail-open: a bad path cannot break rendering.\n }\n }\n}\n\nfunction average(total: number, count: number): number | null {\n return count === 0 ? null : total / count;\n}\n\nfunction debugMetricsEnabled(env: NodeJS.ProcessEnv): boolean {\n if ((env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim().length > 0) return true;\n const value = (env['TERMWRIGHT_DEBUG'] ?? '').trim().toLowerCase();\n return value === '1' || value === 'true' || value === 'api' || value === 'all' || value === 'on';\n}\n\nfunction performanceFile(env: NodeJS.ProcessEnv): string | undefined {\n const path = (env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim();\n return path.length === 0 ? undefined : path;\n}\n\nfunction filePerformanceSink(\n path: string | undefined,\n context: { readonly adapter: string; readonly framework: string; readonly sessionId: string },\n): ((metrics: ProbePerformanceMetrics) => void) | undefined {\n if (path === undefined) return undefined;\n return (metrics) => {\n const record: ProbePerformanceRecord = {\n kind: 'termwright-probe-performance',\n adapter: context.adapter,\n framework: context.framework,\n sessionId: context.sessionId,\n metrics,\n };\n appendFileSync(path, `${JSON.stringify(record)}\\n`, 'utf8');\n };\n}\n\n/**\n * Connect and complete the handshake.\n *\n * Any failure resolves to `null`. A refused socket, malformed acknowledgement\n * or disappearing driver must be indistinguishable from disabled semantics to\n * the application.\n */\nexport async function connectProbe(options: ConnectOptions): Promise<ProbeChannel | null> {\n const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n return new Promise<ProbeChannel | null>((resolve) => {\n let settled = false;\n let socket: Socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n const timer = setTimeout(() => abandon(), timeoutMs);\n timer.unref();\n\n const detach = (): void => {\n clearTimeout(timer);\n for (const event of ['connect', 'data', 'error', 'close']) socket.removeAllListeners(event);\n };\n const abandon = (): void => {\n if (settled) return;\n settled = true;\n detach();\n socket.destroy();\n resolve(null);\n };\n const accept = (session: ChannelSession, pending: readonly unknown[]): void => {\n if (settled) return;\n settled = true;\n detach();\n const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);\n resolve(new ProbeChannel(\n socket,\n session,\n options.token,\n decoder,\n pending,\n options.performanceMetrics ?? (metricsPath !== undefined || debugMetricsEnabled(process.env)),\n filePerformanceSink(metricsPath, {\n adapter: options.adapterName,\n framework: options.probe.framework,\n sessionId: session.sessionId,\n }),\n ));\n };\n\n try {\n socket = createConnection(options.endpoint);\n socket.unref();\n } catch {\n clearTimeout(timer);\n resolve(null);\n return;\n }\n\n socket.on('error', abandon);\n socket.on('close', abandon);\n socket.on('connect', () => {\n try {\n socket.write(\n encodeFrame(\n {\n type: 'hello',\n protocol: options.protocol ?? PROTOCOL_ID,\n token: options.token,\n adapter: { name: options.adapterName, version: options.adapterVersion },\n capabilities: options.capabilities,\n probe: options.probe,\n },\n DEFAULT_LIMITS.maxFrameBytes,\n ),\n );\n } catch {\n abandon();\n }\n });\n\n socket.on('data', (chunk: Buffer) => {\n if (settled) return;\n let messages: readonly unknown[];\n try {\n messages = decoder.push(chunk);\n } catch {\n abandon();\n return;\n }\n if (messages.length === 0) return;\n\n const parsed = parseDriverMessage(messages[0], DEFAULT_LIMITS);\n if (!parsed.ok || parsed.message.type !== 'hello-ack') {\n abandon();\n return;\n }\n const ack = parsed.message;\n if (ack.protocol !== (options.protocol ?? PROTOCOL_ID)) {\n abandon();\n return;\n }\n accept(\n {\n protocol: ack.protocol,\n sessionId: ack.sessionId,\n limits: narrowLimits(ack.limits),\n markerEnabled: ack.marker.enabled,\n },\n messages.slice(1),\n );\n });\n });\n}\n\n/** Adopt the driver's limits, never above our own: it may tighten, not widen. */\nfunction narrowLimits(offered: ProtocolLimits): ProtocolLimits {\n const entries = Object.entries(DEFAULT_LIMITS).map(([key, fallback]) => {\n const value = (offered as unknown as Record<string, unknown>)[key];\n const usable = typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n return [key, usable ? Math.min(value, fallback as number) : fallback];\n });\n return Object.freeze(Object.fromEntries(entries)) as ProtocolLimits;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Shared process-to-driver transport for zero-config JavaScript probes.\n *\n * Framework probes deliberately keep observation and marker placement in\n * their own packages. This module owns only the framework-neutral handshake,\n * full-snapshot publication and fail-closed socket lifecycle.\n */\n\nimport { createConnection, type Socket } from 'node:net';\nimport { appendFileSync } from 'node:fs';\nimport { performance } from 'node:perf_hooks';\nimport {\n freezeEvidenceProviders,\n type EvidenceProviderRegistry,\n type FrozenEvidenceProviderRegistry,\n} from '@termwright/evidence-provider';\nimport {\n createFrameDecoder,\n encodeFrame,\n encodeMarker,\n parseDriverMessage,\n DEFAULT_LIMITS,\n PROTOCOL_ID,\n type ProtocolId,\n type AdapterCapability,\n type AdapterToDriverMessage,\n type FrameDecoder,\n type ProbeInfo,\n type ProtocolLimits,\n type SemanticSnapshot,\n} from '@termwright/protocol';\n\n/** What the driver decided for this session. */\nexport interface ChannelSession {\n readonly protocol: ProtocolId;\n readonly sessionId: string;\n readonly limits: ProtocolLimits;\n readonly markerEnabled: boolean;\n}\n\n/** Connection parameters. */\nexport interface ConnectOptions {\n readonly endpoint: string;\n readonly token: string;\n readonly probe: ProbeInfo;\n readonly capabilities: readonly AdapterCapability[];\n readonly adapterName: string;\n readonly adapterVersion: string;\n /** Isolated application provider registry. Defaults to the process facade. */\n readonly evidenceProviderRegistry?: EvidenceProviderRegistry;\n /** Milliseconds for connect plus handshake. Default 1000. */\n readonly handshakeTimeoutMs?: number;\n /**\n * Collect per-publication counters and timings.\n *\n * Defaults on when `TERMWRIGHT_DEBUG` enables driver diagnostics or an\n * adapter debug file is configured. Explicit false keeps even the timer off.\n */\n readonly performanceMetrics?: boolean;\n /**\n * Append machine-readable performance snapshots here. Defaults to\n * `TERMWRIGHT_DEBUG_FILE`; write failures never affect the application.\n */\n readonly performanceMetricsFile?: string;\n}\n\n/** Debug-only facts the shared transport can observe without guessing. */\nexport interface ProbePerformanceMetrics {\n readonly enabled: boolean;\n readonly fullSnapshots: number;\n readonly semanticBytes: number;\n readonly semanticNodes: number;\n readonly unknownFrameworkNodes: number;\n readonly droppedEvents: number;\n readonly markerRequests: number;\n readonly serializationMicroseconds: number;\n readonly averageBytesPerFrame: number | null;\n readonly averageSemanticNodesPerFrame: number | null;\n readonly averageUnknownFrameworkNodesPerFrame: number | null;\n readonly averageSerializationMicrosecondsPerFrame: number | null;\n /** Present when the framework session supplies its pre-normalized fact count. */\n readonly probeEventsPerFrame: number | null;\n /** Superseded publications reported by a framework-owned marker queue. */\n readonly coalescedEvents: number;\n /** Only the framework-specific sink knows whether a returned marker drained. */\n readonly renderCorrelationRate: null;\n /** Normalization currently happens before the shared transport is called. */\n readonly parentNormalizationMicrosecondsPerFrame: null;\n}\n\ninterface MutableProbePerformanceMetrics {\n fullSnapshots: number;\n semanticBytes: number;\n semanticNodes: number;\n unknownFrameworkNodes: number;\n droppedEvents: number;\n markerRequests: number;\n serializationMicroseconds: number;\n probeEvents: number;\n framesWithProbeEvents: number;\n coalescedEvents: number;\n}\n\nexport interface ProbePublicationMetrics {\n /** Probe IR objects plus render/layout operations observed for this frame. */\n readonly probeEvents?: number;\n}\n\n/** One JSONL record written by an injected probe in debug-file mode. */\nexport interface ProbePerformanceRecord {\n readonly kind: 'termwright-probe-performance';\n readonly adapter: string;\n readonly framework: string;\n readonly sessionId: string;\n readonly metrics: ProbePerformanceMetrics;\n}\n\ninterface SendResult {\n readonly bytes: number;\n readonly serializationMicroseconds: number;\n}\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 1_000;\n\n/**\n * A connected channel.\n *\n * `publish` returns the marker rather than writing it. Only a framework probe\n * knows when its frame bytes have drained, and emitting earlier silently pairs\n * the tree with the wrong screen.\n */\nexport class ProbeChannel {\n readonly #socket: Socket;\n readonly session: ChannelSession;\n readonly #token: string;\n readonly #evidenceProviders: FrozenEvidenceProviderRegistry;\n #open = true;\n #revision = 0;\n readonly #performance: MutableProbePerformanceMetrics | null;\n readonly #performanceSink: (() => void) | null;\n\n /** @internal Built by {@link connectProbe} once the handshake succeeded. */\n constructor(\n socket: Socket,\n session: ChannelSession,\n token: string,\n decoder: FrameDecoder,\n pending: readonly unknown[] = [],\n evidenceProviders: FrozenEvidenceProviderRegistry = freezeEvidenceProviders(),\n performanceMetrics = false,\n performanceSink?: (metrics: ProbePerformanceMetrics) => void,\n ) {\n this.#socket = socket;\n this.session = session;\n this.#token = token;\n this.#evidenceProviders = evidenceProviders;\n this.#performance = performanceMetrics\n ? {\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n probeEvents: 0,\n framesWithProbeEvents: 0,\n coalescedEvents: 0,\n }\n : null;\n this.#performanceSink =\n performanceSink === undefined ? null : () => performanceSink(this.performanceMetrics());\n\n socket.on('data', (chunk: Buffer) => {\n if (!this.#open) return;\n try {\n for (const value of decoder.push(chunk)) this.#dispatch(value);\n } catch {\n this.close();\n }\n });\n socket.on('error', () => this.close());\n socket.on('close', () => this.close());\n\n // The ACK and the driver's first request may share one pipe/TCP chunk. The\n // handshake decoder already removed every complete frame from its buffer,\n // so values after the ACK must be handed over explicitly or they vanish.\n for (const value of pending) this.#dispatch(value);\n }\n\n get isOpen(): boolean {\n return this.#open;\n }\n\n /** Revisions published so far. */\n get revision(): number {\n return this.#revision;\n }\n\n /**\n * A stable, immutable debug snapshot. Disabled collection reports no\n * fabricated zeros for averages that were never observed.\n */\n performanceMetrics(): ProbePerformanceMetrics {\n const metrics = this.#performance;\n if (metrics === null) {\n return Object.freeze({\n enabled: false,\n fullSnapshots: 0,\n semanticBytes: 0,\n semanticNodes: 0,\n unknownFrameworkNodes: 0,\n droppedEvents: 0,\n markerRequests: 0,\n serializationMicroseconds: 0,\n averageBytesPerFrame: null,\n averageSemanticNodesPerFrame: null,\n averageUnknownFrameworkNodesPerFrame: null,\n averageSerializationMicrosecondsPerFrame: null,\n probeEventsPerFrame: null,\n coalescedEvents: 0,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n const frames = metrics.fullSnapshots;\n return Object.freeze({\n enabled: true,\n fullSnapshots: frames,\n semanticBytes: metrics.semanticBytes,\n semanticNodes: metrics.semanticNodes,\n unknownFrameworkNodes: metrics.unknownFrameworkNodes,\n droppedEvents: metrics.droppedEvents,\n markerRequests: metrics.markerRequests,\n serializationMicroseconds: metrics.serializationMicroseconds,\n averageBytesPerFrame: average(metrics.semanticBytes, frames),\n averageSemanticNodesPerFrame: average(metrics.semanticNodes, frames),\n averageUnknownFrameworkNodesPerFrame: average(metrics.unknownFrameworkNodes, frames),\n averageSerializationMicrosecondsPerFrame: average(metrics.serializationMicroseconds, frames),\n probeEventsPerFrame: average(metrics.probeEvents, metrics.framesWithProbeEvents),\n coalescedEvents: metrics.coalescedEvents,\n renderCorrelationRate: null,\n parentNormalizationMicrosecondsPerFrame: null,\n });\n }\n\n /**\n * Send a full tree and commit its revision.\n *\n * Full snapshots satisfy the producer side of dropped-frame recovery by\n * construction. Diff subscription can be added later without weakening this\n * initial correctness floor.\n */\n publish(\n snapshot: SemanticSnapshot,\n publication: ProbePublicationMetrics = {},\n ): string | undefined {\n if (!this.#open) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n return undefined;\n }\n const providerEvidence = this.#evidenceProviders.collect({\n sessionId: snapshot.sessionId,\n revision: snapshot.revision,\n columns: snapshot.columns,\n rows: snapshot.rows,\n resolveRecipient: (recipient) => {\n const matches =\n 'semanticId' in recipient\n ? snapshot.nodes.filter((node) => node.id === recipient.semanticId)\n : 'testId' in recipient\n ? snapshot.nodes.filter((node) => node.testId === recipient.testId)\n : snapshot.nodes.filter(\n (node) => node.role === recipient.role && node.name === recipient.name,\n );\n if (matches.length !== 1) {\n throw new Error(\n `application evidence recipient resolved to ${matches.length} semantic nodes`,\n );\n }\n return matches[0]!.id;\n },\n });\n const qualifiedSnapshot: SemanticSnapshot =\n providerEvidence.length === 0 ? snapshot : Object.freeze({ ...snapshot, providerEvidence });\n const sent = this.#send(\n { type: 'snapshot', snapshot: qualifiedSnapshot },\n this.#performance !== null,\n );\n if (sent === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n this.#revision = snapshot.revision;\n if (this.#performance !== null) {\n this.#performance.fullSnapshots += 1;\n this.#performance.semanticBytes += sent.bytes;\n this.#performance.semanticNodes += snapshot.nodes.length;\n this.#performance.unknownFrameworkNodes += snapshot.nodes.filter(\n (node) => node.role === 'generic',\n ).length;\n this.#performance.serializationMicroseconds += sent.serializationMicroseconds;\n if (\n publication.probeEvents !== undefined &&\n Number.isSafeInteger(publication.probeEvents) &&\n publication.probeEvents >= 0\n ) {\n this.#performance.probeEvents += publication.probeEvents;\n this.#performance.framesWithProbeEvents += 1;\n }\n }\n if (this.#send({ type: 'revision-commit', revision: snapshot.revision }) === undefined) {\n if (this.#performance !== null) this.#performance.droppedEvents += 1;\n this.#emitPerformance();\n return undefined;\n }\n\n if (!this.session.markerEnabled) {\n this.#emitPerformance();\n return undefined;\n }\n try {\n const marker = encodeMarker(this.#token, this.session.sessionId, snapshot.revision);\n if (this.#performance !== null) this.#performance.markerRequests += 1;\n this.#emitPerformance();\n return marker;\n } catch {\n this.#emitPerformance();\n return undefined;\n }\n }\n\n /** Report a framework publication superseded before its marker could drain. */\n recordCoalescedEvent(): void {\n if (this.#performance !== null) {\n this.#performance.coalescedEvents += 1;\n this.#emitPerformance();\n }\n }\n\n /** Report a typed fatal producer-contract violation and close the channel. */\n fail(\n code: 'duplicate-semantic-key' | 'adapter-guarantee-violation' | 'internal',\n message: string,\n ): void {\n if (!this.#open) return;\n this.#send({ type: 'error', code, message: message.slice(0, 1_024) });\n this.close();\n }\n\n /** Disable the channel and release the socket. Idempotent. */\n close(): void {\n if (!this.#open) return;\n this.#open = false;\n this.#evidenceProviders.close();\n this.#socket.removeAllListeners('data');\n this.#socket.destroy();\n }\n\n #dispatch(value: unknown): void {\n const parsed = parseDriverMessage(value, this.session.limits);\n if (!parsed.ok) {\n this.close();\n return;\n }\n const message = parsed.message;\n if (message.type === 'error') {\n this.close();\n return;\n }\n }\n\n #send(message: AdapterToDriverMessage, measureSerialization = false): SendResult | undefined {\n if (!this.#open) return undefined;\n let frame: Uint8Array;\n let serializationMicroseconds: number;\n try {\n const started = measureSerialization ? performance.now() : 0;\n frame = encodeFrame(message, this.session.limits.maxFrameBytes);\n serializationMicroseconds = measureSerialization ? (performance.now() - started) * 1_000 : 0;\n } catch {\n // This is locally produced data and encodeFrame writes nothing before it\n // rejects. Drop only this publication: a later, smaller framework frame\n // can still be valid and must not lose the already negotiated channel.\n return undefined;\n }\n try {\n this.#socket.write(frame);\n return { bytes: frame.byteLength, serializationMicroseconds };\n } catch {\n this.close();\n return undefined;\n }\n }\n\n #emitPerformance(): void {\n try {\n this.#performanceSink?.();\n } catch {\n // Debug output is strictly fail-open: a bad path cannot break rendering.\n }\n }\n}\n\nfunction average(total: number, count: number): number | null {\n return count === 0 ? null : total / count;\n}\n\nfunction debugMetricsEnabled(env: NodeJS.ProcessEnv): boolean {\n if ((env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim().length > 0) return true;\n const value = (env['TERMWRIGHT_DEBUG'] ?? '').trim().toLowerCase();\n return value === '1' || value === 'true' || value === 'api' || value === 'all' || value === 'on';\n}\n\nfunction performanceFile(env: NodeJS.ProcessEnv): string | undefined {\n const path = (env['TERMWRIGHT_DEBUG_FILE'] ?? '').trim();\n return path.length === 0 ? undefined : path;\n}\n\nfunction filePerformanceSink(\n path: string | undefined,\n context: { readonly adapter: string; readonly framework: string; readonly sessionId: string },\n): ((metrics: ProbePerformanceMetrics) => void) | undefined {\n if (path === undefined) return undefined;\n return (metrics) => {\n const record: ProbePerformanceRecord = {\n kind: 'termwright-probe-performance',\n adapter: context.adapter,\n framework: context.framework,\n sessionId: context.sessionId,\n metrics,\n };\n appendFileSync(path, `${JSON.stringify(record)}\\n`, 'utf8');\n };\n}\n\n/**\n * Connect and complete the handshake.\n *\n * Any failure resolves to `null`. A refused socket, malformed acknowledgement\n * or disappearing driver must be indistinguishable from disabled semantics to\n * the application.\n */\nexport async function connectProbe(options: ConnectOptions): Promise<ProbeChannel | null> {\n const timeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n // Freeze synchronously, before a socket can connect and before hello is\n // constructed. Any later application registration is a lifecycle error.\n const evidenceProviders = freezeEvidenceProviders(options.evidenceProviderRegistry);\n\n return new Promise<ProbeChannel | null>((resolve) => {\n let settled = false;\n let socket: Socket;\n const decoder = createFrameDecoder(DEFAULT_LIMITS.maxFrameBytes);\n const timer = setTimeout(() => abandon(), timeoutMs);\n timer.unref();\n\n const detach = (): void => {\n clearTimeout(timer);\n for (const event of ['connect', 'data', 'error', 'close']) socket.removeAllListeners(event);\n };\n const abandon = (): void => {\n if (settled) return;\n settled = true;\n detach();\n socket.destroy();\n evidenceProviders.close();\n resolve(null);\n };\n const accept = (session: ChannelSession, pending: readonly unknown[]): void => {\n if (settled) return;\n settled = true;\n detach();\n const metricsPath = options.performanceMetricsFile ?? performanceFile(process.env);\n resolve(\n new ProbeChannel(\n socket,\n session,\n options.token,\n decoder,\n pending,\n evidenceProviders,\n options.performanceMetrics ??\n (metricsPath !== undefined || debugMetricsEnabled(process.env)),\n filePerformanceSink(metricsPath, {\n adapter: options.adapterName,\n framework: options.probe.framework,\n sessionId: session.sessionId,\n }),\n ),\n );\n };\n\n try {\n socket = createConnection(options.endpoint);\n socket.unref();\n } catch {\n clearTimeout(timer);\n evidenceProviders.close();\n resolve(null);\n return;\n }\n\n socket.on('error', abandon);\n socket.on('close', abandon);\n socket.on('connect', () => {\n try {\n socket.write(\n encodeFrame(\n {\n type: 'hello',\n protocol: PROTOCOL_ID,\n token: options.token,\n adapter: { name: options.adapterName, version: options.adapterVersion },\n capabilities: options.capabilities,\n probe: options.probe,\n ...(evidenceProviders.registrations.length === 0\n ? {}\n : { providers: evidenceProviders.registrations }),\n },\n DEFAULT_LIMITS.maxFrameBytes,\n ),\n );\n } catch {\n abandon();\n }\n });\n\n socket.on('data', (chunk: Buffer) => {\n if (settled) return;\n let messages: readonly unknown[];\n try {\n messages = decoder.push(chunk);\n } catch {\n abandon();\n return;\n }\n if (messages.length === 0) return;\n\n const parsed = parseDriverMessage(messages[0], DEFAULT_LIMITS);\n if (!parsed.ok || parsed.message.type !== 'hello-ack') {\n abandon();\n return;\n }\n const ack = parsed.message;\n if (ack.protocol !== PROTOCOL_ID) {\n abandon();\n return;\n }\n accept(\n {\n protocol: ack.protocol,\n sessionId: ack.sessionId,\n limits: narrowLimits(ack.limits),\n markerEnabled: ack.marker.enabled,\n },\n messages.slice(1),\n );\n });\n });\n}\n\n/** Adopt the driver's limits, never above our own: it may tighten, not widen. */\nfunction narrowLimits(offered: ProtocolLimits): ProtocolLimits {\n const entries = Object.entries(DEFAULT_LIMITS).map(([key, fallback]) => {\n const value = (offered as unknown as Record<string, unknown>)[key];\n const usable = typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n return [key, usable ? Math.min(value, fallback as number) : fallback];\n });\n return Object.freeze(Object.fromEntries(entries)) as ProtocolLimits;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termwright/probe-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "shared fail-closed transport for zero-config JavaScript probes",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "type": "module",
12
12
  "engines": {
13
- "node": ">=22"
13
+ "node": "^22.0.0 || ^24.0.0"
14
14
  },
15
15
  "exports": {
16
16
  ".": {
@@ -22,11 +22,12 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@termwright/protocol": "0.2.0"
25
+ "@termwright/evidence-provider": "0.3.1",
26
+ "@termwright/protocol": "0.3.1"
26
27
  },
27
28
  "scripts": {
28
29
  "build": "tsup src/index.ts --format esm --dts --sourcemap --clean",
29
30
  "typecheck": "tsc --noEmit",
30
- "test": "vitest run"
31
+ "test": "pnpm --dir ../.. test -- -- --run packages/probe-runtime"
31
32
  }
32
33
  }