@sgtbeatdown/wolfpack-tasks 0.0.0-stage → 0.2.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.
@@ -0,0 +1,310 @@
1
+ import { createTaskCore } from "./task-core";
2
+ import type { TaskCore } from "./task-core";
3
+ import { RESET_CODES, RETRY_CODES, taskErrorMessage } from "./task-errors";
4
+ import { TASK_PROTOCOL_VERSION, TaskProtocolError } from "./task-protocol";
5
+ import type { RelayTransportBinding, TaskEndpoint, TaskRelay } from "./task-protocol";
6
+ import type { TaskStore } from "./task-store";
7
+ import { fromWolfpackEnvelope, toWolfpackEnvelope, WOLFPACK_TASK_RELAY_ID } from "./wolfpack-task-relay";
8
+
9
+ export const VOLATILE_PROFILE = "volatile-v1";
10
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11
+ const CURSOR = /^(0|[1-9][0-9]{0,31})$/;
12
+
13
+ export interface VolatileTaskSessionOptions {
14
+ /** Explicit trusted endpoint ingress URL. No default route or installed server. */
15
+ readonly url: string;
16
+ readonly callerSession: string;
17
+ /** Caller owns this endpoint's RAM state and closes it after session.close(). */
18
+ readonly store: TaskStore;
19
+ readonly fetch?: typeof fetch;
20
+ readonly requestTimeoutMs?: number;
21
+ }
22
+ export interface VolatileTaskSession {
23
+ connect(signal?: AbortSignal): Promise<TaskCore>;
24
+ /** Explicitly retire the prior binding; never migrate historical task identities. */
25
+ rebind(signal?: AbortSignal): Promise<TaskCore>;
26
+ status(): { readonly state: "unbound" | "ready" | "reset" | "closed"; readonly binding: RelayTransportBinding | undefined };
27
+ close(): void;
28
+ }
29
+
30
+ /** Epoch-bound transport used by the normal extension; caller owns the store. */
31
+ export function createVolatileTaskSession(options: VolatileTaskSessionOptions): VolatileTaskSession {
32
+ const url = new URL(options.url);
33
+ if (url.username || url.password || url.search || url.hash || !(url.protocol === "https:" || (url.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(url.hostname)))) throw new TypeError("explicit trusted HTTPS or loopback relay URL required");
34
+ if (!text(options.callerSession)) throw new TypeError("caller session is required");
35
+ const timeoutMs = options.requestTimeoutMs ?? 12_000;
36
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 15_000) throw new TypeError("invalid relay request deadline");
37
+ const requestFetch = options.fetch ?? fetch, store = options.store;
38
+ let transport: Transport | undefined, core: TaskCore | undefined, busy = false, closed = false;
39
+ let reset = false;
40
+ const saved = () => store.getRelayTransportBinding();
41
+ const validateSaved = (binding: RelayTransportBinding) => {
42
+ if (binding.profile !== VOLATILE_PROFILE || !UUID.test(binding.epoch) || !localEndpoint(binding.endpoint)
43
+ || !text(binding.generation) || typeof binding.url !== "string" || !text(binding.callerSession) || (binding.reset !== undefined && binding.reset !== true)
44
+ || !same(store.getEndpointBinding(), binding.endpoint)) throw failure("INVALID_RELAY_METADATA", false);
45
+ };
46
+ const quarantine = (prior: TaskEndpoint | undefined, code: string) => {
47
+ if (!prior) return;
48
+ for (const record of store.outbox("pending")) if (same(record.envelope.source, prior)) store.quarantineOutbox(record.envelope.envelopeId, {
49
+ errorCode: code, reason: "relay binding retired; delivery outcome may be unknown", details: { priorEndpoint: prior, mayHaveBeenDelivered: true }, quarantinedAt: Date.now(),
50
+ });
51
+ };
52
+ const onReset = (expected: RelayTransportBinding | undefined) => {
53
+ reset = true;
54
+ const binding = saved();
55
+ // Another controller may already have installed a successor. A stale
56
+ // response must never retire or quarantine that successor's work.
57
+ if (binding && matchesBinding(binding, expected)) store.transaction(() => { store.setRelayTransportBinding({ ...binding, reset: true }); quarantine(binding.endpoint, "RELAY_RESET"); });
58
+ };
59
+ const open = async (replace: boolean, signal?: AbortSignal): Promise<TaskCore> => {
60
+ if (closed) throw failure("RELAY_CLOSED", false);
61
+ if (signal?.aborted) throw failure("ABORTED", true);
62
+ if (busy) throw failure("RELAY_BUSY", true);
63
+ if (!replace && core) { transport!.assertStoredBinding(saved()); await core.connect(signal); return core; }
64
+ busy = true;
65
+ try {
66
+ let prior = saved(), priorEndpoint = store.getEndpointBinding();
67
+ if (prior) validateSaved(prior);
68
+ if (!replace && (reset || prior?.reset || (!prior && priorEndpoint) || (prior && (prior.url !== url.href || prior.callerSession !== options.callerSession)))) { reset = true; throw failure("RELAY_REBIND_REQUIRED", false); }
69
+ if (replace) {
70
+ // Explicit acceptance of loss starts empty. Session history is the only
71
+ // archive; old task identities/ACKs/outbox must never enter the new scope.
72
+ transport?.stop(); core = undefined; reset = true;
73
+ store.clear(); prior = undefined; priorEndpoint = undefined;
74
+ }
75
+ const expectedBinding = saved();
76
+ const generation = replace ? crypto.randomUUID() : prior?.generation ?? store.getEndpointGeneration() ?? crypto.randomUUID();
77
+ store.transaction(() => store.setEndpointGeneration(generation));
78
+ const selected = new Transport(url.href, options.callerSession, generation, replace ? undefined : prior, requestFetch, timeoutMs, onReset);
79
+ transport = selected;
80
+ const binding = await selected.register(signal);
81
+ if (closed) throw failure("RELAY_CLOSED", false);
82
+ store.transaction(() => {
83
+ const current = saved(), currentEndpoint = store.getEndpointBinding();
84
+ const unchanged = expectedBinding ? matchesBinding(current, expectedBinding) && current?.reset === expectedBinding.reset : current === undefined;
85
+ if (!unchanged || store.getEndpointGeneration() !== generation || (priorEndpoint ? !same(currentEndpoint, priorEndpoint) : currentEndpoint !== undefined)) {
86
+ selected.stop(); reset = true; throw failure("RELAY_RESET", false);
87
+ }
88
+ if (replace || !prior) store.setReceiveCursor("0");
89
+ store.setEndpointBinding(binding.endpoint); store.setRelayTransportBinding(binding);
90
+ });
91
+ reset = false;
92
+ // Retained old core handles cannot mutate a successor lifetime even if
93
+ // they resume after await. The RAM store remains controller-owned.
94
+ const guard = () => selected.assertStoredBinding(saved());
95
+ const guarded: TaskStore = { ...store, transaction<T>(operation: () => T): T {
96
+ guard();
97
+ return store.transaction(() => { guard(); const value = operation(); guard(); return value; });
98
+ } };
99
+ const relay = selected.relay();
100
+ const guardedRelay: TaskRelay = { id: relay.id,
101
+ connect: (input, signal) => { guard(); return relay.connect(input, signal); },
102
+ resolve: (input, signal) => { guard(); return relay.resolve(input, signal); },
103
+ send: (input, signal) => { guard(); return relay.send(input, signal); },
104
+ receive: (input, signal) => { guard(); return relay.receive(input, signal); },
105
+ acknowledgeDelivery: (input, signal) => { guard(); return relay.acknowledgeDelivery(input, signal); },
106
+ };
107
+ core = createTaskCore({ endpoint: binding.endpoint, relay: guardedRelay, store: guarded });
108
+ await core.connect(signal);
109
+ return core;
110
+ } finally { busy = false; }
111
+ };
112
+ return {
113
+ connect: signal => open(false, signal), rebind: signal => open(true, signal),
114
+ status: () => ({ state: closed ? "closed" : reset || saved()?.reset ? "reset" : core ? "ready" : "unbound", binding: saved() }),
115
+ close() { closed = true; transport?.stop(); },
116
+ };
117
+ }
118
+
119
+ class Transport {
120
+ private readonly stopSignal = new AbortController();
121
+ private binding: RelayTransportBinding | undefined;
122
+ private expiresAt = 0;
123
+ private renewing: Promise<RelayTransportBinding> | undefined;
124
+ private active = 0;
125
+ private readonly waiters: Array<{ reserved: boolean; grant(): void }> = [];
126
+ constructor(private url: string, private caller: string, private generation: string, prior: RelayTransportBinding | undefined,
127
+ private fetcher: typeof fetch, private timeoutMs: number, private onReset: (expected: RelayTransportBinding | undefined) => void) { this.binding = prior; }
128
+ stop(): void { this.stopSignal.abort(); }
129
+ assertActive(): void { if (this.stopSignal.signal.aborted) throw failure("RELAY_RESET", false, { mayHaveBeenDelivered: true }); }
130
+ assertStoredBinding(binding: RelayTransportBinding | undefined): void {
131
+ this.assertActive();
132
+ if (!matchesBinding(binding, this.binding) || binding?.reset) this.retire();
133
+ }
134
+ private retire(): never { this.stop(); this.onReset(this.binding); throw failure("RELAY_RESET", false, { mayHaveBeenDelivered: true }); }
135
+ async register(signal?: AbortSignal): Promise<RelayTransportBinding> {
136
+ this.assertActive();
137
+ if (this.binding && this.expiresAt - Date.now() > this.timeoutMs + 10_000) return this.binding;
138
+ if (this.renewing) return this.renewing;
139
+ this.renewing = (async () => {
140
+ const reply = await this.request({ operation: "connect", profile: VOLATILE_PROFILE, callerSession: this.caller, generation: this.generation,
141
+ protocolVersions: [2], leaseMs: 60_000, ...(this.binding && { epoch: this.binding.epoch }) }, signal);
142
+ const value = reply.value;
143
+ if (!record(value) || value.kind !== "connected" || !localEndpoint(value.endpoint) || typeof value.leaseExpiresAt !== "string" || !(Date.parse(value.leaseExpiresAt) > Date.now())) throw failure("INVALID_CONNECTION", true);
144
+ if (this.binding && !same(this.binding.endpoint, value.endpoint)) this.retire();
145
+ this.binding = Object.freeze({ profile: VOLATILE_PROFILE, epoch: reply.epoch, endpoint: Object.freeze({ ...value.endpoint }), generation: this.generation, callerSession: this.caller, url: this.url });
146
+ this.expiresAt = Math.min(Date.parse(value.leaseExpiresAt), Date.now() + 60_000);
147
+ return this.binding;
148
+ })();
149
+ try { return await this.renewing; } finally { this.renewing = undefined; }
150
+ }
151
+ private async operation(operation: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<Record<string, unknown>> {
152
+ const binding = await this.register(signal);
153
+ const result = await this.request({ operation, profile: binding.profile, epoch: binding.epoch, endpoint: binding.endpoint, callerSession: this.caller, ...body }, signal);
154
+ if (!record(result.value)) throw failure("INVALID_RESPONSE", true);
155
+ return result.value;
156
+ }
157
+ relay(): TaskRelay {
158
+ const own = (endpoint: TaskEndpoint) => { this.assertActive(); if (!same(endpoint, this.binding?.endpoint)) throw failure("SOURCE_MISMATCH", false); };
159
+ return {
160
+ id: WOLFPACK_TASK_RELAY_ID,
161
+ connect: async (input, signal) => {
162
+ own(input.endpoint);
163
+ if (input.protocolVersion !== TASK_PROTOCOL_VERSION) throw failure("INCOMPATIBLE_PROTOCOL", false);
164
+ await this.register(signal); return { endpoint: input.endpoint, receiveCursor: input.receiveCursor };
165
+ },
166
+ resolve: async (input, signal) => {
167
+ const value = await this.operation("resolve", { target: { relay: input.relay, id: input.reference } }, signal);
168
+ if (value.kind !== "resolved" || !endpoint(value.endpoint) || value.endpoint.relay !== input.relay || value.endpoint.id !== input.reference) throw failure("INVALID_TARGET", true);
169
+ return value.endpoint;
170
+ },
171
+ send: async (input, signal) => {
172
+ own(input.source);
173
+ const envelope = toWolfpackEnvelope(input); // Preserve original persisted wire content.
174
+ const value = await this.operation("send", { envelope }, signal);
175
+ if (value.kind !== "accepted" || value.envelopeId !== input.envelopeId || !uuid(value.acceptanceId) || typeof value.duplicate !== "boolean" || value.forwarding !== (envelope.target.relay === WOLFPACK_TASK_RELAY_ID ? "local" : "forwarded")) throw failure("INVALID_ACCEPTANCE", true);
176
+ return { envelopeId: input.envelopeId };
177
+ },
178
+ receive: async (input, signal) => {
179
+ own(input.endpoint);
180
+ if (!cursor(input.cursor) || !Number.isInteger(input.limit) || input.limit < 1) throw failure("INVALID_CURSOR", false);
181
+ const limit = Math.min(input.limit, 50);
182
+ const value = await this.operation("receive", { cursor: input.cursor, limit }, signal);
183
+ if (value.kind !== "page" || !Array.isArray(value.deliveries) || value.deliveries.length > limit || !cursor(value.nextCursor) || typeof value.hasMore !== "boolean") throw failure("INVALID_INBOX", true);
184
+ let last = BigInt(input.cursor);
185
+ const ids = new Set<string>();
186
+ const deliveries = value.deliveries.map((item: unknown) => {
187
+ if (!record(item) || !cursor(item.cursor) || BigInt(item.cursor) <= last || !record(item.envelope)) throw failure("INVALID_INBOX", true);
188
+ if (!endpoint(item.envelope.source) || !localEndpoint(item.envelope.target)) throw failure("INVALID_INBOX", true);
189
+ const envelope = fromWolfpackEnvelope(item.envelope as unknown as Parameters<typeof fromWolfpackEnvelope>[0]);
190
+ if (!same(envelope.target, input.endpoint) || ids.has(envelope.envelopeId)) throw failure("INVALID_INBOX", true);
191
+ ids.add(envelope.envelopeId); last = BigInt(item.cursor);
192
+ return { cursor: item.cursor, envelope };
193
+ });
194
+ if (value.nextCursor !== (deliveries.at(-1)?.cursor ?? input.cursor) || (!deliveries.length && value.hasMore)) throw failure("INVALID_INBOX", true);
195
+ return { deliveries, nextCursor: value.nextCursor, hasMore: value.hasMore };
196
+ },
197
+ acknowledgeDelivery: async (input, signal) => {
198
+ own(input.endpoint);
199
+ if (!cursor(input.cursor) || !text(input.envelopeId)) throw failure("INVALID_CURSOR", false);
200
+ const value = await this.operation("acknowledge", { envelopeId: input.envelopeId }, signal);
201
+ if (value.kind !== "acknowledged" || typeof value.duplicate !== "boolean") throw failure("INVALID_RESPONSE", true);
202
+ },
203
+ };
204
+ }
205
+ // FIFO in-flight slots: 8 for ordinary requests, 2 more held for acknowledge/connect.
206
+ // A waiter has sent nothing, so its abort is a plain retryable ABORTED; RELAY_CAPACITY
207
+ // only ever comes from the server.
208
+ private fits(reserved: boolean): boolean { return this.active < (reserved ? 10 : 8); }
209
+ private acquire(reserved: boolean, signal?: AbortSignal): Promise<void> {
210
+ if (this.fits(reserved)) { this.active++; return Promise.resolve(); }
211
+ return new Promise((resolve, reject) => {
212
+ const cleanup = () => { signal?.removeEventListener("abort", cancel); this.stopSignal.signal.removeEventListener("abort", cancel); };
213
+ const waiter = { reserved, grant: () => { cleanup(); this.active++; resolve(); } };
214
+ const cancel = () => {
215
+ cleanup(); this.waiters.splice(this.waiters.indexOf(waiter), 1);
216
+ reject(this.stopSignal.signal.aborted ? failure("RELAY_RESET", false, { mayHaveBeenDelivered: true }) : failure("ABORTED", true));
217
+ };
218
+ this.waiters.push(waiter);
219
+ signal?.addEventListener("abort", cancel, { once: true }); this.stopSignal.signal.addEventListener("abort", cancel, { once: true });
220
+ if (signal?.aborted) cancel();
221
+ });
222
+ }
223
+ private release(): void {
224
+ this.active--;
225
+ for (const waiter of [...this.waiters]) if (this.fits(waiter.reserved)) { this.waiters.splice(this.waiters.indexOf(waiter), 1); waiter.grant(); }
226
+ }
227
+ private async request(body: Record<string, unknown>, signal?: AbortSignal): Promise<{ epoch: string; value: unknown }> {
228
+ this.assertActive();
229
+ const reserved = body.operation === "acknowledge" || body.operation === "connect";
230
+ const encoded = JSON.stringify(body);
231
+ if (Buffer.byteLength(encoded) > 64 * 1024) throw failure("PAYLOAD_TOO_LARGE", false);
232
+ await this.acquire(reserved, signal);
233
+ const controller = new AbortController();
234
+ let timedOut = false;
235
+ const abort = () => controller.abort();
236
+ signal?.addEventListener("abort", abort, { once: true }); this.stopSignal.signal.addEventListener("abort", abort, { once: true });
237
+ if (signal?.aborted || this.stopSignal.signal.aborted) abort();
238
+ const timer = setTimeout(() => { timedOut = true; abort(); }, this.timeoutMs);
239
+ const abortError = () => failure(this.stopSignal.signal.aborted ? "RELAY_RESET" : timedOut ? "RELAY_TIMEOUT" : "ABORTED", !this.stopSignal.signal.aborted, { mayHaveBeenDelivered: true });
240
+ let rejectAbort!: () => void;
241
+ const cancelled = new Promise<never>((_, reject) => { rejectAbort = () => reject(abortError()); controller.signal.addEventListener("abort", rejectAbort, { once: true }); if (controller.signal.aborted) rejectAbort(); });
242
+ try {
243
+ const result = await Promise.race([cancelled, (async () => {
244
+ if (controller.signal.aborted) throw abortError();
245
+ const response = await this.fetcher(this.url, { method: "POST", headers: { "content-type": "application/json" }, body: encoded, redirect: "error", signal: controller.signal });
246
+ if (controller.signal.aborted) { void response.body?.cancel().catch(() => undefined); throw abortError(); }
247
+ const rateLimited = response.status === 429;
248
+ if (response.status === 401 || rateLimited) {
249
+ // Admission failures need no relay profile. Best-effort cleanup must not mask their status.
250
+ void response.body?.cancel().catch(() => undefined);
251
+ throw failure(rateLimited ? "RELAY_CAPACITY" : "RELAY_AUTH_REQUIRED", rateLimited);
252
+ }
253
+ const reader = response.body?.getReader();
254
+ if (!reader) throw failure("INVALID_RESPONSE", true);
255
+ const cancel = () => { void reader.cancel().catch(() => undefined); };
256
+ controller.signal.addEventListener("abort", cancel, { once: true });
257
+ const chunks: Uint8Array[] = []; let bytes = 0;
258
+ try {
259
+ while (true) {
260
+ const part = await reader.read();
261
+ if (controller.signal.aborted) throw abortError();
262
+ if (part.done) break;
263
+ bytes += part.value.byteLength;
264
+ if (bytes > 300 * 1024) throw failure("INVALID_RESPONSE", true);
265
+ chunks.push(part.value);
266
+ }
267
+ return { ok: response.ok, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown };
268
+ } finally { controller.signal.removeEventListener("abort", cancel); cancel(); }
269
+ })()]);
270
+ this.assertActive();
271
+ const data = result.value;
272
+ // An explicit permanent profile refusal retires an existing lifetime even
273
+ // when the selected server engine has no volatile epoch to return.
274
+ if (record(data) && (data.profile === VOLATILE_PROFILE || data.profile === "durable-v2")
275
+ && data.ok === false && record(data.error) && data.error.code === "RELAY_PROFILE_REQUIRED" && data.error.retryable === false) {
276
+ if (this.binding) this.retire();
277
+ throw failure("RELAY_PROFILE_REQUIRED", false);
278
+ }
279
+ if (!record(data) || data.profile !== VOLATILE_PROFILE) throw failure("RELAY_PROFILE_REQUIRED", false);
280
+ if (this.binding && uuid(data.epoch) && data.epoch !== this.binding.epoch) this.retire();
281
+ if (data.ok === false && record(data.error) && text(data.error.code) && typeof data.error.retryable === "boolean") {
282
+ if (RESET_CODES.has(data.error.code)) this.retire();
283
+ const epochlessRejection = data.epoch === undefined && ["RELAY_CAPACITY", "RELAY_UNAVAILABLE", "INVALID_REQUEST", "PEER_POLICY_REQUIRED"].includes(data.error.code);
284
+ if ((!uuid(data.epoch) && !epochlessRejection) || (this.binding && uuid(data.epoch) && data.epoch !== this.binding.epoch)) throw failure("INVALID_RESPONSE", true);
285
+ const details = { ...((data.error.mayHaveBeenDelivered === true || data.error.code === "RELAY_UNAVAILABLE") && { mayHaveBeenDelivered: true }),
286
+ ...(typeof data.error.retryAfterMs === "number" && Number.isFinite(data.error.retryAfterMs) && data.error.retryAfterMs >= 0 && { retryAfterMs: data.error.retryAfterMs }) };
287
+ throw failure(data.error.code, data.error.retryable || RETRY_CODES.has(data.error.code), details);
288
+ }
289
+ if (!result.ok || data.ok !== true || !uuid(data.epoch)) throw failure("INVALID_RESPONSE", true);
290
+ return { epoch: data.epoch, value: data.value };
291
+ } catch (error) {
292
+ // Every failure of an admitted request names its relay operation for the status line.
293
+ const known = error instanceof TaskProtocolError ? error : failure("RELAY_UNAVAILABLE", true, { mayHaveBeenDelivered: true });
294
+ throw new TaskProtocolError(known.code, known.message, { retryable: known.retryable, details: { ...known.details, operation: body.operation } });
295
+ } finally {
296
+ clearTimeout(timer); this.release(); signal?.removeEventListener("abort", abort); this.stopSignal.signal.removeEventListener("abort", abort); controller.signal.removeEventListener("abort", rejectAbort);
297
+ }
298
+ }
299
+ }
300
+ function failure(code: string, retryable: boolean, details?: Record<string, unknown>): TaskProtocolError { return new TaskProtocolError(code, taskErrorMessage(code), { retryable, details }); }
301
+ function record(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
302
+ function text(value: unknown): value is string { return typeof value === "string" && value.length > 0 && value.length <= 512; }
303
+ function uuid(value: unknown): value is string { return typeof value === "string" && UUID.test(value); }
304
+ function endpoint(value: unknown): value is TaskEndpoint { return record(value) && text(value.relay) && uuid(value.id); }
305
+ function localEndpoint(value: unknown): value is TaskEndpoint { return endpoint(value) && value.relay === WOLFPACK_TASK_RELAY_ID; }
306
+ function same(a: TaskEndpoint | undefined, b: TaskEndpoint | undefined): boolean { return !!a && !!b && a.relay === b.relay && a.id === b.id; }
307
+ function matchesBinding(a: RelayTransportBinding | undefined, b: RelayTransportBinding | undefined): boolean {
308
+ return !!a && !!b && a.profile === b.profile && a.epoch === b.epoch && a.generation === b.generation && a.callerSession === b.callerSession && a.url === b.url && same(a.endpoint, b.endpoint);
309
+ }
310
+ function cursor(value: unknown): value is string { return typeof value === "string" && CURSOR.test(value); }
@@ -0,0 +1,46 @@
1
+ import { TaskProtocolError } from "./task-protocol";
2
+ import type { RelayEnvelope, TaskEndpoint } from "./task-protocol";
3
+
4
+ export const WOLFPACK_TASK_RELAY_ID = "wolfpack-pi-tasks-v2";
5
+ const PEER = new RegExp(`^${WOLFPACK_TASK_RELAY_ID}:peer:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, "i");
6
+
7
+ /**
8
+ * Project only protocol-defined references from the sender's local namespace
9
+ * into the recipient's namespace. The authenticated transport header supplies
10
+ * the source peer route and destination identity; payload labels cannot do so.
11
+ * Outgoing wire bytes, opaque application payloads and historical rows stay put.
12
+ */
13
+ export function wolfpackEndpointView(source: TaskEndpoint, target: TaskEndpoint, kind: RelayEnvelope["kind"], payload: unknown): unknown {
14
+ if (source.relay === WOLFPACK_TASK_RELAY_ID && target.relay === WOLFPACK_TASK_RELAY_ID) return payload; // Includes origin/self canonical fanout.
15
+ if (!PEER.test(source.relay) || target.relay !== WOLFPACK_TASK_RELAY_ID) invalid();
16
+ if (kind === "intent") return payload; // Intents carry no protocol endpoint references.
17
+ if (!record(payload)) invalid();
18
+ const originalSource = { relay: WOLFPACK_TASK_RELAY_ID, id: source.id };
19
+ const validateSource = (value: unknown): void => {
20
+ if (!endpoint(value) || !same(value, originalSource)) invalid();
21
+ };
22
+ const validateTarget = (value: unknown): TaskEndpoint => {
23
+ if (!endpoint(value) || !PEER.test(value.relay) || value.id !== target.id) invalid();
24
+ return value;
25
+ };
26
+ if (kind === "canonical_event") {
27
+ validateSource(payload.source); validateTarget(payload.target);
28
+ return { ...payload, source, target };
29
+ }
30
+ if (!record(payload.task) || !record(payload.event)) invalid();
31
+ validateSource(payload.task.origin); validateSource(payload.event.source);
32
+ const originalTarget = validateTarget(payload.task.target);
33
+ if (!endpoint(payload.event.target) || !same(payload.event.target, originalTarget)) invalid();
34
+ return {
35
+ ...payload,
36
+ task: { ...payload.task, origin: source, target },
37
+ event: { ...payload.event, source, target },
38
+ };
39
+ }
40
+
41
+ function invalid(): never {
42
+ throw new TaskProtocolError("INVALID_ENDPOINT_REFERENCE", "peer task references do not match the transport's endpoint roles", { retryable: false });
43
+ }
44
+ function record(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
45
+ function endpoint(value: unknown): value is TaskEndpoint { return record(value) && typeof value.relay === "string" && typeof value.id === "string"; }
46
+ function same(a: TaskEndpoint, b: TaskEndpoint): boolean { return a.relay === b.relay && a.id === b.id; }
@@ -0,0 +1,20 @@
1
+ import { createHmac, randomBytes } from "node:crypto";
2
+ import { TaskProtocolError } from "./task-protocol";
3
+
4
+ /** Existing owner-provided JWT configuration, only for the local control server.
5
+ * Never copy this credential to an arbitrary programmatic HTTPS relay URL. */
6
+ export function wolfpackLocalHeaders(url: string, input?: RequestInit["headers"], env: Readonly<Record<string, string | undefined>> = process.env): Headers {
7
+ const headers = new Headers(input), target = new URL(url);
8
+ if (headers.has("authorization") || target.protocol !== "http:" || !["127.0.0.1", "[::1]", "localhost"].includes(target.hostname)) return headers;
9
+ const secret = env.WOLFPACK_JWT_SECRET?.trim();
10
+ if (!secret) return headers;
11
+ if (secret.length < 32) throw new TaskProtocolError("RELAY_AUTH_REQUIRED", "configured Wolfpack JWT secret must contain at least 32 characters", { retryable: false });
12
+ const now = Math.floor(Date.now() / 1000);
13
+ const payload = { iat: now, exp: now + 60, jti: randomBytes(8).toString("hex"),
14
+ ...(env.WOLFPACK_JWT_ISSUER?.trim() && { iss: env.WOLFPACK_JWT_ISSUER.trim() }),
15
+ ...(env.WOLFPACK_JWT_AUDIENCE?.trim() && { aud: env.WOLFPACK_JWT_AUDIENCE.trim() }) };
16
+ const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
17
+ const data = `${encode({ alg: "HS256", typ: "JWT" })}.${encode(payload)}`;
18
+ headers.set("authorization", `Bearer ${data}.${createHmac("sha256", secret).update(data).digest("base64url")}`);
19
+ return headers;
20
+ }
@@ -0,0 +1,39 @@
1
+ import { wolfpackEndpointView } from "./wolfpack-endpoint-view";
2
+ import { INVALID_RELAY_METADATA, TASK_PROTOCOL_VERSION, TaskProtocolError } from "./task-protocol";
3
+ import type { RelayEnvelope, TaskEndpoint } from "./task-protocol";
4
+
5
+ export { WOLFPACK_TASK_RELAY_ID } from "./wolfpack-endpoint-view";
6
+ const WIRE_VERSION = 2;
7
+ interface WolfpackRelayEnvelope {
8
+ readonly envelopeId: string; readonly protocolVersion: number;
9
+ readonly source: TaskEndpoint; readonly target: TaskEndpoint;
10
+ readonly payload: unknown; readonly createdAt: string;
11
+ }
12
+
13
+ /** Wire codec only. The retired durable HTTP adapter is intentionally absent. */
14
+ export function toWolfpackEnvelope(envelope: RelayEnvelope): WolfpackRelayEnvelope {
15
+ const createdAt = envelope.createdAt;
16
+ if (typeof createdAt !== "string" || !Number.isFinite(Date.parse(createdAt)) || new Date(Date.parse(createdAt)).toISOString() !== createdAt) {
17
+ throw new TaskProtocolError(INVALID_RELAY_METADATA, "relay envelope requires an immutable creation timestamp", {
18
+ retryable: false, details: { envelopeId: envelope.envelopeId, reason: createdAt === undefined ? "missing" : "invalid" },
19
+ });
20
+ }
21
+ let payload: unknown;
22
+ try { payload = JSON.parse(envelope.payload) as unknown; }
23
+ catch { throw new TaskProtocolError("INVALID_PAYLOAD", "task relay envelope payload is not valid JSON"); }
24
+ return { envelopeId: envelope.envelopeId, protocolVersion: WIRE_VERSION, source: envelope.source, target: envelope.target,
25
+ payload: { taskId: envelope.taskId, kind: envelope.kind, payload }, createdAt };
26
+ }
27
+
28
+ export function fromWolfpackEnvelope(envelope: WolfpackRelayEnvelope): RelayEnvelope {
29
+ if (!text(envelope.envelopeId) || envelope.protocolVersion !== WIRE_VERSION || !endpoint(envelope.source) || !endpoint(envelope.target)) throw new TaskProtocolError("INVALID_ENVELOPE", "Wolfpack relay returned an invalid envelope");
30
+ const body = envelope.payload;
31
+ if (!record(body) || !text(body.taskId) || !["assignment", "intent", "canonical_event"].includes(String(body.kind)) || !("payload" in body)) throw new TaskProtocolError("INVALID_ENVELOPE", "Wolfpack relay payload does not identify a task envelope");
32
+ const kind = body.kind as RelayEnvelope["kind"];
33
+ const payload = JSON.stringify(wolfpackEndpointView(envelope.source, envelope.target, kind, body.payload));
34
+ if (payload === undefined) throw new TaskProtocolError("INVALID_PAYLOAD", "Wolfpack relay envelope payload is not JSON-serializable");
35
+ return { envelopeId: envelope.envelopeId, protocolVersion: TASK_PROTOCOL_VERSION, source: envelope.source, target: envelope.target, taskId: body.taskId, kind, payload, createdAt: envelope.createdAt };
36
+ }
37
+ const text = (value: unknown): value is string => typeof value === "string" && value.length > 0;
38
+ const record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
39
+ const endpoint = (value: unknown): value is TaskEndpoint => record(value) && text(value.relay) && text(value.id);
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "types": ["bun"],
6
+ "target": "ESNext",
7
+ "module": "Preserve",
8
+ "moduleDetection": "force",
9
+ "jsx": "react-jsx",
10
+ "allowJs": true,
11
+
12
+ // Bundler mode
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "verbatimModuleSyntax": true,
16
+ "noEmit": true,
17
+
18
+ // Best practices
19
+ "strict": true,
20
+ "skipLibCheck": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedIndexedAccess": true,
23
+ "noImplicitOverride": true,
24
+
25
+ // Some stricter flags (disabled by default)
26
+ "noUnusedLocals": false,
27
+ "noUnusedParameters": false,
28
+ "noPropertyAccessFromIndexSignature": false
29
+ }
30
+ }