@kraken-e2e/signaling 0.1.0
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/LICENSE +674 -0
- package/dist/bus.d.ts +89 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/bus.js +220 -0
- package/dist/bus.js.map +1 -0
- package/dist/chaos-transport.d.ts +34 -0
- package/dist/chaos-transport.d.ts.map +1 -0
- package/dist/chaos-transport.js +64 -0
- package/dist/chaos-transport.js.map +1 -0
- package/dist/conformance.d.ts +18 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +156 -0
- package/dist/conformance.js.map +1 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +47 -0
- package/dist/errors.js.map +1 -0
- package/dist/in-memory-transport.d.ts +21 -0
- package/dist/in-memory-transport.d.ts.map +1 -0
- package/dist/in-memory-transport.js +142 -0
- package/dist/in-memory-transport.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/near-miss.d.ts +6 -0
- package/dist/near-miss.d.ts.map +1 -0
- package/dist/near-miss.js +30 -0
- package/dist/near-miss.js.map +1 -0
- package/dist/redis-transport.d.ts +46 -0
- package/dist/redis-transport.d.ts.map +1 -0
- package/dist/redis-transport.js +173 -0
- package/dist/redis-transport.js.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
package/dist/bus.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { SignalPayload, SignalRecord, SignalScope, SignalTransport } from './types.js';
|
|
2
|
+
export interface WaitOptions<P extends SignalPayload = SignalPayload> {
|
|
3
|
+
/** Explicit by design — there is no library-level default (ADR-0003 D4). */
|
|
4
|
+
readonly timeoutMs: number;
|
|
5
|
+
/** Only accept signals published by this subscriber. */
|
|
6
|
+
readonly from?: string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Client-side payload filter. A predicate-REJECTED record is permanently
|
|
9
|
+
* consumed for this (subscriber, name) cursor — deterministic and documented
|
|
10
|
+
* (ADR-0003 D3). There is no non-consuming peek in v1.
|
|
11
|
+
*/
|
|
12
|
+
readonly where?: ((payload: P) => boolean) | undefined;
|
|
13
|
+
readonly signal?: AbortSignal | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface SignalBusOptions {
|
|
16
|
+
/** Cap on the serialized payload size. Default 64 KiB (ADR-0003 D5). */
|
|
17
|
+
readonly maxPayloadBytes?: number;
|
|
18
|
+
/** Per-scope record count that triggers a one-time warning. Default 10 000 (ADR-0003 D6). */
|
|
19
|
+
readonly scopeRecordWarnThreshold?: number;
|
|
20
|
+
/** Where warnings go (the orchestrator wires this to its logger). */
|
|
21
|
+
readonly onWarning?: ((message: string) => void) | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The facade every consumer touches. Owns what transports deliberately do not:
|
|
25
|
+
* per-(subscriber, signal-name) cursors, predicates, payload validation, and
|
|
26
|
+
* enriched timeout diagnostics (ADR-0003 D2/D3).
|
|
27
|
+
*/
|
|
28
|
+
export declare class SignalBus {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(transport: SignalTransport, options?: SignalBusOptions);
|
|
31
|
+
scope(scope: SignalScope): ScopedSignals;
|
|
32
|
+
ping(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare class ScopedSignals {
|
|
35
|
+
#private;
|
|
36
|
+
constructor(transport: SignalTransport, scope: SignalScope, options: Required<Pick<SignalBusOptions, 'maxPayloadBytes' | 'scopeRecordWarnThreshold'>> & Pick<SignalBusOptions, 'onWarning'>);
|
|
37
|
+
get scope(): SignalScope;
|
|
38
|
+
/** Creates the scope on the transport. Must be called before any publish/wait. */
|
|
39
|
+
open(): Promise<void>;
|
|
40
|
+
forActor(subscriberId: string): ActorSignals;
|
|
41
|
+
history(): Promise<readonly SignalRecord[]>;
|
|
42
|
+
/** Idempotent. Rejects all pending waiters with ScopeClosedError. */
|
|
43
|
+
destroy(): Promise<void>;
|
|
44
|
+
/** @internal used by ActorSignals */
|
|
45
|
+
publishAs<P extends SignalPayload>(subscriberId: string, name: string, payload: P | undefined): Promise<SignalRecord<P>>;
|
|
46
|
+
/** @internal used by ActorSignals */
|
|
47
|
+
waitAs<P extends SignalPayload>(subscriberId: string, name: string, opts: WaitOptions<P>): Promise<SignalRecord<P>>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The interface consumers program against (`ctx.signals`). Lets orchestrators
|
|
51
|
+
* wrap the concrete ActorSignals (e.g. to emit events) without inheritance.
|
|
52
|
+
*/
|
|
53
|
+
export interface SignalHandle {
|
|
54
|
+
readonly subscriberId: string;
|
|
55
|
+
publish<P extends SignalPayload>(name: string, payload?: P): Promise<SignalRecord<P>>;
|
|
56
|
+
waitFor<P extends SignalPayload>(name: string, opts: WaitOptions<P>): Promise<SignalRecord<P>>;
|
|
57
|
+
barrier(name: string, opts: {
|
|
58
|
+
participants: readonly string[];
|
|
59
|
+
timeoutMs: number;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The per-actor handle steps receive as `ctx.signals`. Binds the subscriber
|
|
65
|
+
* identity (defaults to the actor id — ADR-0001 §5.7, ADR-0003 D3).
|
|
66
|
+
*/
|
|
67
|
+
export declare class ActorSignals implements SignalHandle {
|
|
68
|
+
#private;
|
|
69
|
+
constructor(scoped: ScopedSignals, subscriberId: string);
|
|
70
|
+
get subscriberId(): string;
|
|
71
|
+
/** Fire-and-persist. Never blocks on receivers; resolves once durably ordered. */
|
|
72
|
+
publish<P extends SignalPayload>(name: string, payload?: P): Promise<SignalRecord<P>>;
|
|
73
|
+
/**
|
|
74
|
+
* Exactly one record per call: replay-first, then live; FIFO per
|
|
75
|
+
* (this subscriber, name). Publish-before-wait always delivers — the
|
|
76
|
+
* Kraken v2 lost-signal race is defined away (ADR-0003 D2/D3).
|
|
77
|
+
*/
|
|
78
|
+
waitFor<P extends SignalPayload>(name: string, opts: WaitOptions<P>): Promise<SignalRecord<P>>;
|
|
79
|
+
/**
|
|
80
|
+
* Rendezvous sugar on publish+waitFor: publishes `${name}:${self}` and waits
|
|
81
|
+
* for every other participant's `${name}:${participant}`.
|
|
82
|
+
*/
|
|
83
|
+
barrier(name: string, opts: {
|
|
84
|
+
participants: readonly string[];
|
|
85
|
+
timeoutMs: number;
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"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"}
|
package/dist/bus.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { ScopeClosedError, SignalPayloadError, SignalTimeoutError, SignalUsageError, } from './errors.js';
|
|
2
|
+
import { nearMissNames } from './near-miss.js';
|
|
3
|
+
const DEFAULT_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
4
|
+
const DEFAULT_WARN_THRESHOLD = 10_000;
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
/**
|
|
7
|
+
* The facade every consumer touches. Owns what transports deliberately do not:
|
|
8
|
+
* per-(subscriber, signal-name) cursors, predicates, payload validation, and
|
|
9
|
+
* enriched timeout diagnostics (ADR-0003 D2/D3).
|
|
10
|
+
*/
|
|
11
|
+
export class SignalBus {
|
|
12
|
+
#transport;
|
|
13
|
+
#options;
|
|
14
|
+
constructor(transport, options = {}) {
|
|
15
|
+
this.#transport = transport;
|
|
16
|
+
this.#options = {
|
|
17
|
+
maxPayloadBytes: options.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES,
|
|
18
|
+
scopeRecordWarnThreshold: options.scopeRecordWarnThreshold ?? DEFAULT_WARN_THRESHOLD,
|
|
19
|
+
onWarning: options.onWarning,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
scope(scope) {
|
|
23
|
+
return new ScopedSignals(this.#transport, scope, this.#options);
|
|
24
|
+
}
|
|
25
|
+
ping() {
|
|
26
|
+
return this.#transport.ping();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class ScopedSignals {
|
|
30
|
+
#transport;
|
|
31
|
+
#scope;
|
|
32
|
+
#options;
|
|
33
|
+
/**
|
|
34
|
+
* Cursor per (subscriberId, signalName, from-filter) — the load-bearing
|
|
35
|
+
* state (ADR-0003 D3). The from-filter is part of the channel key: a
|
|
36
|
+
* from-filtered wait must never advance past OTHER senders' records
|
|
37
|
+
* (they were never delivered to this waiter).
|
|
38
|
+
*/
|
|
39
|
+
#cursors = new Map();
|
|
40
|
+
/** Pending wait channels, to reject concurrent identical waits (collision-free keys). */
|
|
41
|
+
#pendingWaits = new Set();
|
|
42
|
+
#publishCount = 0;
|
|
43
|
+
#warned = false;
|
|
44
|
+
constructor(transport, scope, options) {
|
|
45
|
+
this.#transport = transport;
|
|
46
|
+
this.#scope = scope;
|
|
47
|
+
this.#options = options;
|
|
48
|
+
}
|
|
49
|
+
get scope() {
|
|
50
|
+
return this.#scope;
|
|
51
|
+
}
|
|
52
|
+
/** Creates the scope on the transport. Must be called before any publish/wait. */
|
|
53
|
+
open() {
|
|
54
|
+
return this.#transport.createScope(this.#scope);
|
|
55
|
+
}
|
|
56
|
+
forActor(subscriberId) {
|
|
57
|
+
return new ActorSignals(this, subscriberId);
|
|
58
|
+
}
|
|
59
|
+
history() {
|
|
60
|
+
return this.#transport.history(this.#scope);
|
|
61
|
+
}
|
|
62
|
+
/** Idempotent. Rejects all pending waiters with ScopeClosedError. */
|
|
63
|
+
destroy() {
|
|
64
|
+
return this.#transport.destroyScope(this.#scope);
|
|
65
|
+
}
|
|
66
|
+
static #channel(name, from) {
|
|
67
|
+
return JSON.stringify([name, from ?? null]);
|
|
68
|
+
}
|
|
69
|
+
#cursor(subscriberId, channel) {
|
|
70
|
+
return this.#cursors.get(subscriberId)?.get(channel) ?? 0;
|
|
71
|
+
}
|
|
72
|
+
#advanceCursor(subscriberId, channel, seq) {
|
|
73
|
+
let byChannel = this.#cursors.get(subscriberId);
|
|
74
|
+
if (!byChannel) {
|
|
75
|
+
byChannel = new Map();
|
|
76
|
+
this.#cursors.set(subscriberId, byChannel);
|
|
77
|
+
}
|
|
78
|
+
byChannel.set(channel, seq);
|
|
79
|
+
}
|
|
80
|
+
/** @internal used by ActorSignals */
|
|
81
|
+
async publishAs(subscriberId, name, payload) {
|
|
82
|
+
const normalized = (payload ?? null);
|
|
83
|
+
let serialized;
|
|
84
|
+
try {
|
|
85
|
+
serialized = JSON.stringify(normalized);
|
|
86
|
+
}
|
|
87
|
+
catch (cause) {
|
|
88
|
+
throw new SignalPayloadError(`Payload of signal "${name}" is not JSON-serializable: ` +
|
|
89
|
+
`${cause instanceof Error ? cause.message : String(cause)}. ` +
|
|
90
|
+
'Signals may carry only plain JSON data — never functions, sessions, or cyclic objects.');
|
|
91
|
+
}
|
|
92
|
+
if (serialized === undefined) {
|
|
93
|
+
throw new SignalPayloadError(`Payload of signal "${name}" is not JSON-serializable.`);
|
|
94
|
+
}
|
|
95
|
+
const bytes = encoder.encode(serialized).length;
|
|
96
|
+
if (bytes > this.#options.maxPayloadBytes) {
|
|
97
|
+
throw new SignalPayloadError(`Payload of signal "${name}" is ${bytes} bytes; the cap is ${this.#options.maxPayloadBytes}. ` +
|
|
98
|
+
'Signals coordinate actors — they are not a data pipe. Pass a reference (id, path) instead.');
|
|
99
|
+
}
|
|
100
|
+
const record = await this.#transport.publish(this.#scope, {
|
|
101
|
+
name,
|
|
102
|
+
from: subscriberId,
|
|
103
|
+
payload: normalized,
|
|
104
|
+
});
|
|
105
|
+
this.#publishCount += 1;
|
|
106
|
+
if (!this.#warned && this.#publishCount > this.#options.scopeRecordWarnThreshold) {
|
|
107
|
+
this.#warned = true;
|
|
108
|
+
this.#options.onWarning?.(`Signal scope ${this.#scope.runId}/${this.#scope.scenarioId} exceeded ` +
|
|
109
|
+
`${this.#options.scopeRecordWarnThreshold} records — a runaway publish loop?`);
|
|
110
|
+
}
|
|
111
|
+
return record;
|
|
112
|
+
}
|
|
113
|
+
/** @internal used by ActorSignals */
|
|
114
|
+
async waitAs(subscriberId, name, opts) {
|
|
115
|
+
const channel = ScopedSignals.#channel(name, opts.from);
|
|
116
|
+
// Collision-free key: quoted-alias subscriber ids may contain spaces.
|
|
117
|
+
const pendingKey = JSON.stringify([subscriberId, channel]);
|
|
118
|
+
if (this.#pendingWaits.has(pendingKey)) {
|
|
119
|
+
throw new SignalUsageError(`Subscriber "${subscriberId}" is already waiting for signal "${name}". ` +
|
|
120
|
+
'Concurrent identical waits are ambiguous (which record goes to whom?) and are ' +
|
|
121
|
+
'rejected by design (ADR-0003 D3). Wait sequentially to count, or use distinct ' +
|
|
122
|
+
'subscribers to broadcast.');
|
|
123
|
+
}
|
|
124
|
+
this.#pendingWaits.add(pendingKey);
|
|
125
|
+
const startedAt = Date.now();
|
|
126
|
+
try {
|
|
127
|
+
for (;;) {
|
|
128
|
+
const elapsed = Date.now() - startedAt;
|
|
129
|
+
const remaining = opts.timeoutMs - elapsed;
|
|
130
|
+
if (remaining <= 0) {
|
|
131
|
+
throw await this.#timeoutError(subscriberId, name, channel, opts.timeoutMs);
|
|
132
|
+
}
|
|
133
|
+
let record;
|
|
134
|
+
try {
|
|
135
|
+
record = await this.#transport.waitFor(this.#scope, { name, afterSeq: this.#cursor(subscriberId, channel), from: opts.from }, { timeoutMs: remaining, signal: opts.signal });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (error instanceof SignalTimeoutError) {
|
|
139
|
+
// Enrich the transport-level error with subscriber identity.
|
|
140
|
+
throw new SignalTimeoutError({
|
|
141
|
+
...error.detail,
|
|
142
|
+
subscriberId,
|
|
143
|
+
timeoutMs: opts.timeoutMs,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
// Predicate-rejected records are consumed for this cursor (ADR-0003 D3).
|
|
149
|
+
this.#advanceCursor(subscriberId, channel, record.seq);
|
|
150
|
+
if (opts.where && !opts.where(record.payload)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
return record;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
this.#pendingWaits.delete(pendingKey);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async #timeoutError(subscriberId, name, channel, timeoutMs) {
|
|
161
|
+
let snapshot = [];
|
|
162
|
+
try {
|
|
163
|
+
snapshot = await this.#transport.history(this.#scope);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (!(error instanceof ScopeClosedError))
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
return new SignalTimeoutError({
|
|
170
|
+
scope: this.#scope,
|
|
171
|
+
subscriberId,
|
|
172
|
+
signalName: name,
|
|
173
|
+
timeoutMs,
|
|
174
|
+
cursor: this.#cursor(subscriberId, channel),
|
|
175
|
+
historySnapshot: snapshot,
|
|
176
|
+
nearMissNames: nearMissNames(name, snapshot),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The per-actor handle steps receive as `ctx.signals`. Binds the subscriber
|
|
182
|
+
* identity (defaults to the actor id — ADR-0001 §5.7, ADR-0003 D3).
|
|
183
|
+
*/
|
|
184
|
+
export class ActorSignals {
|
|
185
|
+
#scoped;
|
|
186
|
+
#subscriberId;
|
|
187
|
+
constructor(scoped, subscriberId) {
|
|
188
|
+
this.#scoped = scoped;
|
|
189
|
+
this.#subscriberId = subscriberId;
|
|
190
|
+
}
|
|
191
|
+
get subscriberId() {
|
|
192
|
+
return this.#subscriberId;
|
|
193
|
+
}
|
|
194
|
+
/** Fire-and-persist. Never blocks on receivers; resolves once durably ordered. */
|
|
195
|
+
publish(name, payload) {
|
|
196
|
+
return this.#scoped.publishAs(this.#subscriberId, name, payload);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Exactly one record per call: replay-first, then live; FIFO per
|
|
200
|
+
* (this subscriber, name). Publish-before-wait always delivers — the
|
|
201
|
+
* Kraken v2 lost-signal race is defined away (ADR-0003 D2/D3).
|
|
202
|
+
*/
|
|
203
|
+
waitFor(name, opts) {
|
|
204
|
+
return this.#scoped.waitAs(this.#subscriberId, name, opts);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Rendezvous sugar on publish+waitFor: publishes `${name}:${self}` and waits
|
|
208
|
+
* for every other participant's `${name}:${participant}`.
|
|
209
|
+
*/
|
|
210
|
+
async barrier(name, opts) {
|
|
211
|
+
await this.publish(`${name}:${this.#subscriberId}`);
|
|
212
|
+
await Promise.all(opts.participants
|
|
213
|
+
.filter((participant) => participant !== this.#subscriberId)
|
|
214
|
+
.map((participant) => this.waitFor(`${name}:${participant}`, {
|
|
215
|
+
timeoutMs: opts.timeoutMs,
|
|
216
|
+
signal: opts.signal,
|
|
217
|
+
})));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=bus.js.map
|
package/dist/bus.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus.js","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SignalPayload, SignalQuery, SignalRecord, SignalScope, SignalTransport, TransportWaitOptions } from './types.js';
|
|
2
|
+
export type ChaosOperation = 'createScope' | 'publish' | 'waitFor' | 'history' | 'destroyScope' | 'ping';
|
|
3
|
+
export interface ChaosOptions {
|
|
4
|
+
/** Fixed latency, or [min, max] range sampled with `random`. Default 0. */
|
|
5
|
+
readonly latencyMs?: number | readonly [min: number, max: number];
|
|
6
|
+
/** Injectable randomness for deterministic tests. Default Math.random. */
|
|
7
|
+
readonly random?: () => number;
|
|
8
|
+
/**
|
|
9
|
+
* Scripted failures: return true to make the nth call (1-based, per
|
|
10
|
+
* operation) throw TransportUnavailableError instead of delegating.
|
|
11
|
+
*/
|
|
12
|
+
readonly shouldFail?: (operation: ChaosOperation, nthCall: number) => boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Decorator that makes any transport behave like a flaky network: injected
|
|
16
|
+
* latency and scripted TransportUnavailableError failures. Ships in Phase 1 so
|
|
17
|
+
* orchestrator tests exercise distributed-mode failure paths years before a
|
|
18
|
+
* real device farm exists (ADR-0003 D7).
|
|
19
|
+
*/
|
|
20
|
+
export declare class ChaosTransport implements SignalTransport {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(inner: SignalTransport, options?: ChaosOptions);
|
|
23
|
+
createScope(scope: SignalScope): Promise<void>;
|
|
24
|
+
publish(scope: SignalScope, signal: {
|
|
25
|
+
name: string;
|
|
26
|
+
from: string;
|
|
27
|
+
payload: SignalPayload;
|
|
28
|
+
}): Promise<SignalRecord>;
|
|
29
|
+
waitFor(scope: SignalScope, query: SignalQuery, opts: TransportWaitOptions): Promise<SignalRecord>;
|
|
30
|
+
history(scope: SignalScope): Promise<readonly SignalRecord[]>;
|
|
31
|
+
destroyScope(scope: SignalScope): Promise<void>;
|
|
32
|
+
ping(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=chaos-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chaos-transport.d.ts","sourceRoot":"","sources":["../src/chaos-transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACX,eAAe,EACf,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,SAAS,GACT,SAAS,GACT,SAAS,GACT,cAAc,GACd,MAAM,CAAC;AAEX,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAClE,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/E;AAKD;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,eAAe;;gBAKxC,KAAK,EAAE,eAAe,EAAE,OAAO,GAAE,YAAiB;IA2BxD,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,OAAO,CACX,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE,GAC7D,OAAO,CAAC,YAAY,CAAC;IAKlB,OAAO,CACX,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,YAAY,CAAC;IAOlB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAK7D,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAI5B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { TransportUnavailableError } from './errors.js';
|
|
2
|
+
const sleep = (ms) => ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
+
/**
|
|
4
|
+
* Decorator that makes any transport behave like a flaky network: injected
|
|
5
|
+
* latency and scripted TransportUnavailableError failures. Ships in Phase 1 so
|
|
6
|
+
* orchestrator tests exercise distributed-mode failure paths years before a
|
|
7
|
+
* real device farm exists (ADR-0003 D7).
|
|
8
|
+
*/
|
|
9
|
+
export class ChaosTransport {
|
|
10
|
+
#inner;
|
|
11
|
+
#options;
|
|
12
|
+
#callCounts = new Map();
|
|
13
|
+
constructor(inner, options = {}) {
|
|
14
|
+
this.#inner = inner;
|
|
15
|
+
this.#options = options;
|
|
16
|
+
}
|
|
17
|
+
#latency() {
|
|
18
|
+
const { latencyMs } = this.#options;
|
|
19
|
+
if (latencyMs === undefined)
|
|
20
|
+
return 0;
|
|
21
|
+
if (typeof latencyMs === 'number')
|
|
22
|
+
return latencyMs;
|
|
23
|
+
const [min, max] = latencyMs;
|
|
24
|
+
const random = this.#options.random ?? Math.random;
|
|
25
|
+
return min + random() * (max - min);
|
|
26
|
+
}
|
|
27
|
+
async #interfere(operation) {
|
|
28
|
+
const nthCall = (this.#callCounts.get(operation) ?? 0) + 1;
|
|
29
|
+
this.#callCounts.set(operation, nthCall);
|
|
30
|
+
const latency = this.#latency();
|
|
31
|
+
await sleep(latency);
|
|
32
|
+
if (this.#options.shouldFail?.(operation, nthCall)) {
|
|
33
|
+
throw new TransportUnavailableError(`Injected chaos: transport unavailable on ${operation} (call #${nthCall}).`);
|
|
34
|
+
}
|
|
35
|
+
return latency;
|
|
36
|
+
}
|
|
37
|
+
async createScope(scope) {
|
|
38
|
+
await this.#interfere('createScope');
|
|
39
|
+
return this.#inner.createScope(scope);
|
|
40
|
+
}
|
|
41
|
+
async publish(scope, signal) {
|
|
42
|
+
await this.#interfere('publish');
|
|
43
|
+
return this.#inner.publish(scope, signal);
|
|
44
|
+
}
|
|
45
|
+
async waitFor(scope, query, opts) {
|
|
46
|
+
// Latency counts against the waiter-local budget (ADR-0003 D4).
|
|
47
|
+
const latency = await this.#interfere('waitFor');
|
|
48
|
+
const remaining = Math.max(1, opts.timeoutMs - latency);
|
|
49
|
+
return this.#inner.waitFor(scope, query, { ...opts, timeoutMs: remaining });
|
|
50
|
+
}
|
|
51
|
+
async history(scope) {
|
|
52
|
+
await this.#interfere('history');
|
|
53
|
+
return this.#inner.history(scope);
|
|
54
|
+
}
|
|
55
|
+
async destroyScope(scope) {
|
|
56
|
+
await this.#interfere('destroyScope');
|
|
57
|
+
return this.#inner.destroyScope(scope);
|
|
58
|
+
}
|
|
59
|
+
async ping() {
|
|
60
|
+
await this.#interfere('ping');
|
|
61
|
+
return this.#inner.ping();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=chaos-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chaos-transport.js","sourceRoot":"","sources":["../src/chaos-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SignalTransport } from './types.js';
|
|
2
|
+
export interface ConformanceOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Baseline wait budget for tests that must NOT time out. Raise for
|
|
5
|
+
* high-latency transports (e.g. a chaos-wrapped or networked one).
|
|
6
|
+
*/
|
|
7
|
+
readonly generousTimeoutMs?: number;
|
|
8
|
+
/** Budget for tests that MUST time out. Raise for high-latency transports. */
|
|
9
|
+
readonly shortTimeoutMs?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Register the whole suite as skipped (external infrastructure absent —
|
|
12
|
+
* e.g. no redis-server on this machine). Skipping is VISIBLE in the test
|
|
13
|
+
* summary; silently not registering the suite would hide the gap.
|
|
14
|
+
*/
|
|
15
|
+
readonly skip?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function describeSignalTransportContract(name: string, makeTransport: () => SignalTransport | Promise<SignalTransport>, options?: ConformanceOptions): void;
|
|
18
|
+
//# sourceMappingURL=conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,YAAY,CAAC;AAQ/D,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,EAC/D,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAwLN"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport conformance suite (ADR-0003 D7). Every SignalTransport — first-party
|
|
3
|
+
* or student-written, in-memory or networked — must pass this suite before use.
|
|
4
|
+
* This file, more than any document, is what keeps signal semantics stable
|
|
5
|
+
* across thesis-student rotation.
|
|
6
|
+
*
|
|
7
|
+
* Usage (inside a vitest suite):
|
|
8
|
+
* import { describeSignalTransportContract } from '@kraken-e2e/signaling/conformance';
|
|
9
|
+
* describeSignalTransportContract('InMemoryTransport', () => new InMemoryTransport());
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { ScopeClosedError, SignalTimeoutError, SignalWaitAbortedError } from './errors.js';
|
|
13
|
+
let scopeCounter = 0;
|
|
14
|
+
function freshScope() {
|
|
15
|
+
scopeCounter += 1;
|
|
16
|
+
return { runId: 'conformance-run', scenarioId: `scenario-${scopeCounter}` };
|
|
17
|
+
}
|
|
18
|
+
export function describeSignalTransportContract(name, makeTransport, options = {}) {
|
|
19
|
+
const GENEROUS = options.generousTimeoutMs ?? 2_000;
|
|
20
|
+
const SHORT = options.shortTimeoutMs ?? 60;
|
|
21
|
+
describe.skipIf(options.skip === true)(`SignalTransport conformance: ${name}`, () => {
|
|
22
|
+
async function openScope() {
|
|
23
|
+
const transport = await makeTransport();
|
|
24
|
+
const scope = freshScope();
|
|
25
|
+
await transport.createScope(scope);
|
|
26
|
+
return { transport, scope };
|
|
27
|
+
}
|
|
28
|
+
it('1. replays a signal published BEFORE the wait started (the Kraken v2 race)', async () => {
|
|
29
|
+
const { transport, scope } = await openScope();
|
|
30
|
+
await transport.publish(scope, { name: 'message-sent', from: 'alice', payload: { n: 1 } });
|
|
31
|
+
const record = await transport.waitFor(scope, { name: 'message-sent', afterSeq: 0 }, { timeoutMs: GENEROUS });
|
|
32
|
+
expect(record.name).toBe('message-sent');
|
|
33
|
+
expect(record.from).toBe('alice');
|
|
34
|
+
expect(record.payload).toEqual({ n: 1 });
|
|
35
|
+
});
|
|
36
|
+
it('2. assigns a strictly increasing per-scope total order', async () => {
|
|
37
|
+
const { transport, scope } = await openScope();
|
|
38
|
+
const first = await transport.publish(scope, { name: 'a', from: 'x', payload: null });
|
|
39
|
+
const second = await transport.publish(scope, { name: 'b', from: 'x', payload: null });
|
|
40
|
+
const third = await transport.publish(scope, { name: 'a', from: 'y', payload: null });
|
|
41
|
+
expect(second.seq).toBeGreaterThan(first.seq);
|
|
42
|
+
expect(third.seq).toBeGreaterThan(second.seq);
|
|
43
|
+
const history = await transport.history(scope);
|
|
44
|
+
expect([...history].map((r) => r.seq)).toEqual([...history].map((r) => r.seq).sort((a, b) => a - b));
|
|
45
|
+
});
|
|
46
|
+
it('3. serves records FIFO as afterSeq advances (loop counting)', async () => {
|
|
47
|
+
const { transport, scope } = await openScope();
|
|
48
|
+
for (let i = 1; i <= 3; i += 1) {
|
|
49
|
+
await transport.publish(scope, { name: 'tick', from: 'loop', payload: i });
|
|
50
|
+
}
|
|
51
|
+
let cursor = 0;
|
|
52
|
+
const seen = [];
|
|
53
|
+
for (let i = 0; i < 3; i += 1) {
|
|
54
|
+
const record = await transport.waitFor(scope, { name: 'tick', afterSeq: cursor }, { timeoutMs: GENEROUS });
|
|
55
|
+
cursor = record.seq;
|
|
56
|
+
seen.push(record.payload);
|
|
57
|
+
}
|
|
58
|
+
expect(seen).toEqual([1, 2, 3]);
|
|
59
|
+
});
|
|
60
|
+
it('4. broadcasts: independent queries each receive the same record', async () => {
|
|
61
|
+
const { transport, scope } = await openScope();
|
|
62
|
+
const waitA = transport.waitFor(scope, { name: 'go', afterSeq: 0 }, { timeoutMs: GENEROUS });
|
|
63
|
+
const waitB = transport.waitFor(scope, { name: 'go', afterSeq: 0 }, { timeoutMs: GENEROUS });
|
|
64
|
+
await transport.publish(scope, { name: 'go', from: 'alice', payload: 'now' });
|
|
65
|
+
const [recordA, recordB] = await Promise.all([waitA, waitB]);
|
|
66
|
+
expect(recordA.seq).toBe(recordB.seq);
|
|
67
|
+
expect(recordA.payload).toBe('now');
|
|
68
|
+
expect(recordB.payload).toBe('now');
|
|
69
|
+
});
|
|
70
|
+
it('5. filters by publisher when `from` is given', async () => {
|
|
71
|
+
const { transport, scope } = await openScope();
|
|
72
|
+
await transport.publish(scope, { name: 'done', from: 'mallory', payload: 'wrong' });
|
|
73
|
+
const pending = transport.waitFor(scope, { name: 'done', afterSeq: 0, from: 'alice' }, { timeoutMs: GENEROUS });
|
|
74
|
+
await transport.publish(scope, { name: 'done', from: 'alice', payload: 'right' });
|
|
75
|
+
const record = await pending;
|
|
76
|
+
expect(record.from).toBe('alice');
|
|
77
|
+
expect(record.payload).toBe('right');
|
|
78
|
+
});
|
|
79
|
+
it('6. isolates scopes completely', async () => {
|
|
80
|
+
const transport = await makeTransport();
|
|
81
|
+
const scopeA = freshScope();
|
|
82
|
+
const scopeB = freshScope();
|
|
83
|
+
await transport.createScope(scopeA);
|
|
84
|
+
await transport.createScope(scopeB);
|
|
85
|
+
await transport.publish(scopeA, { name: 'leak?', from: 'a', payload: null });
|
|
86
|
+
await expect(transport.waitFor(scopeB, { name: 'leak?', afterSeq: 0 }, { timeoutMs: SHORT })).rejects.toBeInstanceOf(SignalTimeoutError);
|
|
87
|
+
expect(await transport.history(scopeB)).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
it('7. never resolves in the same synchronous execution (Zalgo prevention)', async () => {
|
|
90
|
+
const { transport, scope } = await openScope();
|
|
91
|
+
await transport.publish(scope, { name: 'ready', from: 'a', payload: null });
|
|
92
|
+
let sameTick = true;
|
|
93
|
+
const settled = transport
|
|
94
|
+
.waitFor(scope, { name: 'ready', afterSeq: 0 }, { timeoutMs: GENEROUS })
|
|
95
|
+
.then(() => sameTick);
|
|
96
|
+
sameTick = false;
|
|
97
|
+
// If the implementation resolved before returning control, the then-callback
|
|
98
|
+
// would still observe sameTick === false (promise semantics), so we also
|
|
99
|
+
// verify publish acknowledgement defers: no waiter callback may have run yet.
|
|
100
|
+
expect(await settled).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
it('8. isolates payloads by value (JSON round-trip; mutation does not propagate)', async () => {
|
|
103
|
+
const { transport, scope } = await openScope();
|
|
104
|
+
const payload = { items: ['a'] };
|
|
105
|
+
const published = await transport.publish(scope, { name: 'data', from: 'a', payload });
|
|
106
|
+
payload.items.push('MUTATED');
|
|
107
|
+
const received = await transport.waitFor(scope, { name: 'data', afterSeq: 0 }, { timeoutMs: GENEROUS });
|
|
108
|
+
expect(received.payload).toEqual({ items: ['a'] });
|
|
109
|
+
expect(published.payload).toEqual({ items: ['a'] });
|
|
110
|
+
expect(received.payload).not.toBe(payload);
|
|
111
|
+
});
|
|
112
|
+
it('9. rejects with SignalTimeoutError once the budget elapses — and not much before', async () => {
|
|
113
|
+
const { transport, scope } = await openScope();
|
|
114
|
+
const startedAt = Date.now();
|
|
115
|
+
await expect(transport.waitFor(scope, { name: 'never', afterSeq: 0 }, { timeoutMs: SHORT })).rejects.toBeInstanceOf(SignalTimeoutError);
|
|
116
|
+
const elapsed = Date.now() - startedAt;
|
|
117
|
+
expect(elapsed).toBeGreaterThanOrEqual(SHORT - 15);
|
|
118
|
+
});
|
|
119
|
+
it('10. cancels a pending wait promptly through its AbortSignal', async () => {
|
|
120
|
+
const { transport, scope } = await openScope();
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const pending = transport.waitFor(scope, { name: 'never', afterSeq: 0 }, { timeoutMs: GENEROUS, signal: controller.signal });
|
|
123
|
+
controller.abort();
|
|
124
|
+
await expect(pending).rejects.toBeInstanceOf(SignalWaitAbortedError);
|
|
125
|
+
});
|
|
126
|
+
it('11. destroyScope rejects pending waiters, is idempotent, and closes the scope', async () => {
|
|
127
|
+
const { transport, scope } = await openScope();
|
|
128
|
+
const pending = transport.waitFor(scope, { name: 'never', afterSeq: 0 }, { timeoutMs: GENEROUS });
|
|
129
|
+
await transport.destroyScope(scope);
|
|
130
|
+
await expect(pending).rejects.toBeInstanceOf(ScopeClosedError);
|
|
131
|
+
await transport.destroyScope(scope); // idempotent
|
|
132
|
+
await expect(transport.publish(scope, { name: 'x', from: 'a', payload: null })).rejects.toBeInstanceOf(ScopeClosedError);
|
|
133
|
+
await expect(transport.waitFor(scope, { name: 'x', afterSeq: 0 }, { timeoutMs: SHORT })).rejects.toBeInstanceOf(ScopeClosedError);
|
|
134
|
+
await expect(transport.history(scope)).rejects.toBeInstanceOf(ScopeClosedError);
|
|
135
|
+
});
|
|
136
|
+
it('12. ping resolves on a healthy transport', async () => {
|
|
137
|
+
const transport = await makeTransport();
|
|
138
|
+
await expect(transport.ping()).resolves.toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
it('13. timeout errors carry the full history snapshot and near-miss suggestions', async () => {
|
|
141
|
+
const { transport, scope } = await openScope();
|
|
142
|
+
await transport.publish(scope, { name: 'message-sent', from: 'alice', payload: null });
|
|
143
|
+
try {
|
|
144
|
+
await transport.waitFor(scope, { name: 'mesage-sent', afterSeq: 0 }, { timeoutMs: SHORT });
|
|
145
|
+
expect.unreachable('waitFor must time out');
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
expect(error).toBeInstanceOf(SignalTimeoutError);
|
|
149
|
+
const detail = error.detail;
|
|
150
|
+
expect(detail.historySnapshot.map((r) => r.name)).toContain('message-sent');
|
|
151
|
+
expect(detail.nearMissNames).toContain('message-sent');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.js","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"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"}
|