stitchkit 0.76.1 → 0.77.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/dist/agent-runtime-coding-tools.js +1 -2
- package/dist/agent-runtime-harness.js +4 -5
- package/dist/agent-runtime.js +7 -8
- package/dist/application/decisions.d.ts +86 -0
- package/dist/application/decisions.d.ts.map +1 -0
- package/dist/application/kernel.d.ts +33 -1
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/watch-hub.d.ts +1 -1
- package/dist/application/watch-hub.d.ts.map +1 -1
- package/dist/application-schemas.d.ts +15 -0
- package/dist/application-schemas.d.ts.map +1 -0
- package/dist/application-schemas.js +111 -0
- package/dist/application.d.ts +3 -2
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +184 -114
- package/dist/cli.js +6 -7
- package/dist/contract/index.js +1 -2
- package/dist/{index-v5dm4bwq.js → index-58jzmnn4.js} +1 -1
- package/dist/{index-885chjxq.js → index-7qy2ex0m.js} +1 -1
- package/dist/index-a916km3r.js +45 -0
- package/dist/{index-28jsdwt1.js → index-aczggrty.js} +1 -1
- package/dist/{index-65kathdm.js → index-da1aqnhb.js} +4 -6
- package/dist/{index-xgahrwdf.js → index-ezmn6ac6.js} +3 -3
- package/dist/{index-4kp4fxvp.js → index-f6n5n7nz.js} +18 -4
- package/dist/{index-6wr9f6qm.js → index-jqtsc9mj.js} +2 -2
- package/dist/{index-9c5dbr5q.js → index-k2zczx1g.js} +1 -1
- package/dist/{index-v7xjfy10.js → index-m668wzyc.js} +1 -1
- package/dist/{index-e0a57ymn.js → index-nemjkxjp.js} +1 -1
- package/dist/{index-9my2n66s.js → index-s2rchahr.js} +4 -4
- package/dist/{index-k68e5jz0.js → index-s4c8wy8m.js} +2 -2
- package/dist/{index-52400rdd.js → index-t8qyvrvg.js} +3 -3
- package/dist/{index-endzd8sj.js → index-vbf2p6me.js} +1 -1
- package/dist/{index-hvftzz91.js → index-vc1b0b1b.js} +221 -117
- package/dist/{index-hsz2mmk0.js → index-vj3vvpaa.js} +17 -1
- package/dist/index.js +1 -2
- package/dist/internal/decision.d.ts +31 -0
- package/dist/internal/decision.d.ts.map +1 -0
- package/dist/internal/stable-digest.d.ts +40 -6
- package/dist/internal/stable-digest.d.ts.map +1 -1
- package/dist/live/events.d.ts +8 -13
- package/dist/live/events.d.ts.map +1 -1
- package/dist/live/watch-client.d.ts.map +1 -1
- package/dist/live.d.ts +1 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +40 -57
- package/dist/node.js +4 -5
- package/dist/observability/index.js +3 -4
- package/dist/primitives.js +1 -2
- package/dist/remote.js +2 -3
- package/dist/server/event-bus.d.ts +3 -2
- package/dist/server/event-bus.d.ts.map +1 -1
- package/dist/server/index.js +5 -6
- package/dist/testing.js +3 -4
- package/dist/tool-invoker.js +5 -6
- package/dist/tools.js +13 -14
- package/llms-full.txt +184 -4
- package/package.json +6 -2
- package/dist/index-rxfy4cq7.js +0 -17
- package/dist/index-vdvmcy7r.js +0 -22
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
collectTools,
|
|
3
3
|
createToolRunner
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-7qy2ex0m.js";
|
|
5
5
|
import {
|
|
6
6
|
toolErrorFromResult
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-s4c8wy8m.js";
|
|
8
8
|
import {
|
|
9
9
|
assertUniqueToolName
|
|
10
10
|
} from "./index-22by16v6.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
collectToolSurface
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-k2zczx1g.js";
|
|
4
4
|
import {
|
|
5
5
|
createToolRunner,
|
|
6
6
|
formatToolError
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-7qy2ex0m.js";
|
|
8
8
|
import {
|
|
9
9
|
coerceJsonArgs,
|
|
10
10
|
toolResultFromError
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-s4c8wy8m.js";
|
|
12
12
|
import {
|
|
13
13
|
isWithinDir
|
|
14
14
|
} from "./index-sbdmyz75.js";
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import {
|
|
28
28
|
isUnsafeKey,
|
|
29
29
|
safeJsonParse
|
|
30
|
-
} from "./index-
|
|
30
|
+
} from "./index-vj3vvpaa.js";
|
|
31
31
|
import {
|
|
32
32
|
isRecord
|
|
33
33
|
} from "./index-qyrqwr4c.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getRequestContext,
|
|
3
3
|
runWithRequestContext
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-nemjkxjp.js";
|
|
5
5
|
import {
|
|
6
6
|
objectShapeKeys
|
|
7
7
|
} from "./index-22by16v6.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
isUnsafeKey,
|
|
15
15
|
safeJsonParse
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-vj3vvpaa.js";
|
|
17
17
|
import {
|
|
18
18
|
AppError,
|
|
19
19
|
STITCH_ERROR_STATUS,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
defaultSignalSource,
|
|
8
8
|
guardSignalCallback,
|
|
9
9
|
reportSignalError
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-vbf2p6me.js";
|
|
11
11
|
import {
|
|
12
12
|
extractIp,
|
|
13
13
|
getClientInfo,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
runWithRequestContext,
|
|
19
19
|
setRequestEndpoint,
|
|
20
20
|
setRequestError
|
|
21
|
-
} from "./index-
|
|
21
|
+
} from "./index-nemjkxjp.js";
|
|
22
22
|
import {
|
|
23
23
|
ShutdownOptionsSchema,
|
|
24
24
|
isStreamCancellation,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
joinRoutePath,
|
|
40
40
|
parseTrailingWildcard,
|
|
41
41
|
safeJsonParse
|
|
42
|
-
} from "./index-
|
|
42
|
+
} from "./index-vj3vvpaa.js";
|
|
43
43
|
import {
|
|
44
44
|
AppError,
|
|
45
45
|
badRequest
|
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
AppError
|
|
14
14
|
} from "./index-0w9abg87.js";
|
|
15
15
|
|
|
16
|
+
// src/application/kernel.ts
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
16
19
|
// src/application/graph.ts
|
|
17
20
|
function resolveResourceGraph(resources) {
|
|
18
21
|
const entries = resources.map((resource, declarationIndex) => ({
|
|
@@ -82,6 +85,15 @@ var ApplicationShutdownBudgetSchema = ShutdownOptionsSchema.pick({
|
|
|
82
85
|
gracePeriodMs: true,
|
|
83
86
|
forceTimeoutMs: true
|
|
84
87
|
});
|
|
88
|
+
var ApplicationRestartInputSchema = z.object({ resourceId: z.string().min(1) }).strict().readonly();
|
|
89
|
+
var ApplicationRestartOutcomeSchema = z.enum(["restarted", "failed", "refused"]);
|
|
90
|
+
var ApplicationRestartResultSchema = z.object({
|
|
91
|
+
resourceId: z.string(),
|
|
92
|
+
affected: z.array(z.string()).readonly(),
|
|
93
|
+
outcome: ApplicationRestartOutcomeSchema,
|
|
94
|
+
reason: z.string().optional(),
|
|
95
|
+
durationMs: z.number().int().nonnegative()
|
|
96
|
+
}).strict().readonly();
|
|
85
97
|
|
|
86
98
|
class ApplicationAdmissionError extends AppError {
|
|
87
99
|
constructor() {
|
|
@@ -294,138 +306,221 @@ function createApplication(config) {
|
|
|
294
306
|
publish();
|
|
295
307
|
}
|
|
296
308
|
};
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
const startEach = async (entries, signal) => {
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
if (shutdownRequested || signal.aborted) {
|
|
312
|
+
throw new ApplicationStartupInterruptedError;
|
|
313
|
+
}
|
|
314
|
+
const record = records.get(entry.id);
|
|
315
|
+
if (!record)
|
|
316
|
+
throw new Error("Managed resource record disappeared");
|
|
317
|
+
const dependencyFailed = entry.dependsOn.some((dependencyId) => records.get(dependencyId)?.state !== "ready");
|
|
318
|
+
if (dependencyFailed) {
|
|
319
|
+
record.state = "failed";
|
|
320
|
+
record.health = "unhealthy";
|
|
321
|
+
record.failures.push("start");
|
|
322
|
+
publish();
|
|
323
|
+
if (entry.required) {
|
|
324
|
+
throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable dependency`);
|
|
325
|
+
}
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
record.attempted = true;
|
|
329
|
+
record.state = "starting";
|
|
330
|
+
publish();
|
|
331
|
+
try {
|
|
332
|
+
const started = await entry.resource.start(contextFor(record, { signal }));
|
|
333
|
+
if (shutdownRequested || signal.aborted) {
|
|
304
334
|
throw new ApplicationStartupInterruptedError;
|
|
305
335
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
336
|
+
if (isStartResult(started)) {
|
|
337
|
+
record.runtime = started;
|
|
338
|
+
if (started.value !== undefined)
|
|
339
|
+
published.set(entry.id, started.value);
|
|
340
|
+
let resourceReady = started.ready === undefined;
|
|
341
|
+
let completionSettled = false;
|
|
342
|
+
let completionFailure;
|
|
343
|
+
const completion = started.completion?.then(() => {
|
|
344
|
+
completionSettled = true;
|
|
345
|
+
if (resourceReady)
|
|
346
|
+
markLateCompletion(record);
|
|
347
|
+
}, (error) => {
|
|
348
|
+
completionSettled = true;
|
|
349
|
+
completionFailure = error;
|
|
350
|
+
if (resourceReady)
|
|
351
|
+
markLateCompletion(record, { error });
|
|
352
|
+
});
|
|
353
|
+
if (started.ready && completion) {
|
|
354
|
+
const readiness = started.ready.then(() => "ready");
|
|
355
|
+
const completionBeforeReady = completion.then(() => "completion");
|
|
356
|
+
const first = await Promise.race([readiness, completionBeforeReady]);
|
|
357
|
+
if (first === "completion") {
|
|
358
|
+
throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
|
|
359
|
+
}
|
|
360
|
+
resourceReady = true;
|
|
361
|
+
if (completionSettled) {
|
|
362
|
+
throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
|
|
363
|
+
}
|
|
364
|
+
} else if (started.ready) {
|
|
365
|
+
await started.ready;
|
|
366
|
+
resourceReady = true;
|
|
367
|
+
} else if (completion) {}
|
|
368
|
+
}
|
|
369
|
+
if (shutdownRequested || signal.aborted) {
|
|
370
|
+
throw new ApplicationStartupInterruptedError;
|
|
371
|
+
}
|
|
372
|
+
record.state = "ready";
|
|
373
|
+
if (!record.healthReported) {
|
|
374
|
+
record.health = "healthy";
|
|
375
|
+
record.everHealthy = true;
|
|
376
|
+
}
|
|
377
|
+
publish();
|
|
378
|
+
} catch (error) {
|
|
379
|
+
const interrupted = shutdownRequested || signal.aborted;
|
|
380
|
+
if (!(error instanceof ApplicationStartupInterruptedError)) {
|
|
381
|
+
record.failures.push(error instanceof ResourceCompletionBeforeReadyError ? "completion" : record.runtime?.ready ? "ready" : "start");
|
|
311
382
|
record.state = "failed";
|
|
312
383
|
record.health = "unhealthy";
|
|
313
|
-
record.failures.
|
|
384
|
+
reportFailure(entry.id, record.failures[record.failures.length - 1] ?? "start", error);
|
|
314
385
|
publish();
|
|
315
|
-
if (entry.required) {
|
|
316
|
-
throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable dependency`);
|
|
317
|
-
}
|
|
318
|
-
continue;
|
|
319
386
|
}
|
|
320
|
-
|
|
321
|
-
|
|
387
|
+
if (interrupted || entry.required)
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
const activateEach = async (entries, signal) => {
|
|
393
|
+
for (const entry of entries) {
|
|
394
|
+
if (shutdownRequested || signal.aborted) {
|
|
395
|
+
throw new ApplicationStartupInterruptedError;
|
|
396
|
+
}
|
|
397
|
+
const record = records.get(entry.id);
|
|
398
|
+
if (record?.state !== "ready")
|
|
399
|
+
continue;
|
|
400
|
+
const dependencyUnavailable = entry.dependsOn.some((dependencyId) => {
|
|
401
|
+
const dependency = records.get(dependencyId);
|
|
402
|
+
return dependency?.state !== "ready" || !dependency.activated;
|
|
403
|
+
});
|
|
404
|
+
if (dependencyUnavailable) {
|
|
405
|
+
record.failures.push("start");
|
|
406
|
+
record.state = "failed";
|
|
407
|
+
record.health = "unhealthy";
|
|
322
408
|
publish();
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (shutdownRequested || startupAbort.signal.aborted) {
|
|
326
|
-
throw new ApplicationStartupInterruptedError;
|
|
327
|
-
}
|
|
328
|
-
if (isStartResult(started)) {
|
|
329
|
-
record.runtime = started;
|
|
330
|
-
if (started.value !== undefined)
|
|
331
|
-
published.set(entry.id, started.value);
|
|
332
|
-
let resourceReady = started.ready === undefined;
|
|
333
|
-
let completionSettled = false;
|
|
334
|
-
let completionFailure;
|
|
335
|
-
const completion = started.completion?.then(() => {
|
|
336
|
-
completionSettled = true;
|
|
337
|
-
if (resourceReady)
|
|
338
|
-
markLateCompletion(record);
|
|
339
|
-
}, (error) => {
|
|
340
|
-
completionSettled = true;
|
|
341
|
-
completionFailure = error;
|
|
342
|
-
if (resourceReady)
|
|
343
|
-
markLateCompletion(record, { error });
|
|
344
|
-
});
|
|
345
|
-
if (started.ready && completion) {
|
|
346
|
-
const readiness = started.ready.then(() => "ready");
|
|
347
|
-
const completionBeforeReady = completion.then(() => "completion");
|
|
348
|
-
const first = await Promise.race([readiness, completionBeforeReady]);
|
|
349
|
-
if (first === "completion") {
|
|
350
|
-
throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
|
|
351
|
-
}
|
|
352
|
-
resourceReady = true;
|
|
353
|
-
if (completionSettled) {
|
|
354
|
-
throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
|
|
355
|
-
}
|
|
356
|
-
} else if (started.ready) {
|
|
357
|
-
await started.ready;
|
|
358
|
-
resourceReady = true;
|
|
359
|
-
} else if (completion) {}
|
|
360
|
-
}
|
|
361
|
-
if (shutdownRequested || startupAbort.signal.aborted) {
|
|
362
|
-
throw new ApplicationStartupInterruptedError;
|
|
363
|
-
}
|
|
364
|
-
record.state = "ready";
|
|
365
|
-
if (!record.healthReported) {
|
|
366
|
-
record.health = "healthy";
|
|
367
|
-
record.everHealthy = true;
|
|
368
|
-
}
|
|
369
|
-
publish();
|
|
370
|
-
} catch (error) {
|
|
371
|
-
const interrupted = shutdownRequested || startupAbort.signal.aborted;
|
|
372
|
-
if (!(error instanceof ApplicationStartupInterruptedError)) {
|
|
373
|
-
record.failures.push(error instanceof ResourceCompletionBeforeReadyError ? "completion" : record.runtime?.ready ? "ready" : "start");
|
|
374
|
-
record.state = "failed";
|
|
375
|
-
record.health = "unhealthy";
|
|
376
|
-
reportFailure(entry.id, record.failures[record.failures.length - 1] ?? "start", error);
|
|
377
|
-
publish();
|
|
378
|
-
}
|
|
379
|
-
if (interrupted || entry.required)
|
|
380
|
-
throw error;
|
|
409
|
+
if (entry.required) {
|
|
410
|
+
throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable activation dependency`);
|
|
381
411
|
}
|
|
412
|
+
continue;
|
|
382
413
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (shutdownRequested || startupAbort.signal.aborted) {
|
|
414
|
+
try {
|
|
415
|
+
await entry.resource.activate?.(contextFor(record));
|
|
416
|
+
if (shutdownRequested || signal.aborted) {
|
|
387
417
|
throw new ApplicationStartupInterruptedError;
|
|
388
418
|
}
|
|
389
|
-
|
|
390
|
-
if (record
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (
|
|
419
|
+
record.activated = true;
|
|
420
|
+
if (entry.required && (record.state !== "ready" || record.health !== "healthy")) {
|
|
421
|
+
const observed = `${record.state}/${record.health}`;
|
|
422
|
+
throw new Error(record.everHealthy ? `[stitchkit] required resource "${entry.id}" lost readiness during activation (${observed})` : `[stitchkit] required resource "${entry.id}" is not healthy (${observed}). A required resource must be healthy for the application to be ready; a resource that is expected to start degraded belongs behind \`required: false\`.`);
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
const interrupted = shutdownRequested || signal.aborted;
|
|
426
|
+
if (!(error instanceof ApplicationStartupInterruptedError)) {
|
|
397
427
|
record.failures.push("start");
|
|
398
428
|
record.state = "failed";
|
|
399
429
|
record.health = "unhealthy";
|
|
430
|
+
reportFailure(entry.id, "start", error);
|
|
400
431
|
publish();
|
|
401
|
-
if (entry.required) {
|
|
402
|
-
throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable activation dependency`);
|
|
403
|
-
}
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
try {
|
|
407
|
-
await entry.resource.activate?.(contextFor(record));
|
|
408
|
-
if (shutdownRequested || startupAbort.signal.aborted) {
|
|
409
|
-
throw new ApplicationStartupInterruptedError;
|
|
410
|
-
}
|
|
411
|
-
record.activated = true;
|
|
412
|
-
if (entry.required && (record.state !== "ready" || record.health !== "healthy")) {
|
|
413
|
-
const observed = `${record.state}/${record.health}`;
|
|
414
|
-
throw new Error(record.everHealthy ? `[stitchkit] required resource "${entry.id}" lost readiness during activation (${observed})` : `[stitchkit] required resource "${entry.id}" is not healthy (${observed}). A required resource must be healthy for the application to be ready; a resource that is expected to start degraded belongs behind \`required: false\`.`);
|
|
415
|
-
}
|
|
416
|
-
} catch (error) {
|
|
417
|
-
const interrupted = shutdownRequested || startupAbort.signal.aborted;
|
|
418
|
-
if (!(error instanceof ApplicationStartupInterruptedError)) {
|
|
419
|
-
record.failures.push("start");
|
|
420
|
-
record.state = "failed";
|
|
421
|
-
record.health = "unhealthy";
|
|
422
|
-
reportFailure(entry.id, "start", error);
|
|
423
|
-
publish();
|
|
424
|
-
}
|
|
425
|
-
if (interrupted || entry.required)
|
|
426
|
-
throw error;
|
|
427
432
|
}
|
|
433
|
+
if (interrupted || entry.required)
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const subtreeOf = (resourceId) => {
|
|
439
|
+
const affected = new Set([resourceId]);
|
|
440
|
+
for (const entry of ordered) {
|
|
441
|
+
if (entry.dependsOn.some((dependencyId) => affected.has(dependencyId))) {
|
|
442
|
+
affected.add(entry.id);
|
|
428
443
|
}
|
|
444
|
+
}
|
|
445
|
+
return ordered.filter((entry) => affected.has(entry.id));
|
|
446
|
+
};
|
|
447
|
+
const closeOne = async (entry, record, signal) => {
|
|
448
|
+
const context = () => contextFor(record, { signal });
|
|
449
|
+
if (record.attempted && !record.closed) {
|
|
450
|
+
await entry.resource.stopAdmission?.(context());
|
|
451
|
+
await entry.resource.drain?.(context());
|
|
452
|
+
record.closeInvoked = true;
|
|
453
|
+
await entry.resource.close?.(context());
|
|
454
|
+
}
|
|
455
|
+
record.closed = false;
|
|
456
|
+
record.closeInvoked = false;
|
|
457
|
+
record.attempted = false;
|
|
458
|
+
record.activated = false;
|
|
459
|
+
record.state = "registered";
|
|
460
|
+
record.health = "unknown";
|
|
461
|
+
record.healthReported = false;
|
|
462
|
+
record.runtime = undefined;
|
|
463
|
+
published.delete(entry.id);
|
|
464
|
+
};
|
|
465
|
+
let restarting = Promise.resolve();
|
|
466
|
+
const runRestart = async (input) => {
|
|
467
|
+
const startedAt = Date.now();
|
|
468
|
+
const parsed = ApplicationRestartInputSchema.parse(input);
|
|
469
|
+
const affected = subtreeOf(parsed.resourceId);
|
|
470
|
+
const affectedIds = affected.map((entry) => entry.id);
|
|
471
|
+
const refuse = (reason) => ({
|
|
472
|
+
resourceId: parsed.resourceId,
|
|
473
|
+
affected: affectedIds,
|
|
474
|
+
outcome: "refused",
|
|
475
|
+
reason,
|
|
476
|
+
durationMs: Date.now() - startedAt
|
|
477
|
+
});
|
|
478
|
+
if (!records.has(parsed.resourceId)) {
|
|
479
|
+
return refuse(`no resource is registered as "${parsed.resourceId}"`);
|
|
480
|
+
}
|
|
481
|
+
if (shutdownRequested) {
|
|
482
|
+
return refuse("the application is shutting down");
|
|
483
|
+
}
|
|
484
|
+
if (lifecycle !== "ready") {
|
|
485
|
+
return refuse(`the application is ${lifecycle}, not ready`);
|
|
486
|
+
}
|
|
487
|
+
const restartAbort = new AbortController;
|
|
488
|
+
try {
|
|
489
|
+
for (const entry of [...affected].reverse()) {
|
|
490
|
+
const record = records.get(entry.id);
|
|
491
|
+
if (record)
|
|
492
|
+
await closeOne(entry, record, restartAbort.signal);
|
|
493
|
+
}
|
|
494
|
+
publish();
|
|
495
|
+
await startEach(affected, restartAbort.signal);
|
|
496
|
+
await activateEach(affected, restartAbort.signal);
|
|
497
|
+
publish();
|
|
498
|
+
return {
|
|
499
|
+
resourceId: parsed.resourceId,
|
|
500
|
+
affected: affectedIds,
|
|
501
|
+
outcome: "restarted",
|
|
502
|
+
durationMs: Date.now() - startedAt
|
|
503
|
+
};
|
|
504
|
+
} catch (error) {
|
|
505
|
+
publish();
|
|
506
|
+
return {
|
|
507
|
+
resourceId: parsed.resourceId,
|
|
508
|
+
affected: affectedIds,
|
|
509
|
+
outcome: "failed",
|
|
510
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
511
|
+
durationMs: Date.now() - startedAt
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const runStart = async () => {
|
|
516
|
+
lifecycle = "starting";
|
|
517
|
+
publish();
|
|
518
|
+
let startFailure;
|
|
519
|
+
try {
|
|
520
|
+
await startEach(ordered, startupAbort.signal);
|
|
521
|
+
lifecycle = "ready";
|
|
522
|
+
publish();
|
|
523
|
+
await activateEach(ordered, startupAbort.signal);
|
|
429
524
|
if (shutdownRequested) {
|
|
430
525
|
throw new ApplicationStartupInterruptedError;
|
|
431
526
|
}
|
|
@@ -714,8 +809,17 @@ function createApplication(config) {
|
|
|
714
809
|
listener(snapshot());
|
|
715
810
|
return () => listeners.delete(listener);
|
|
716
811
|
},
|
|
717
|
-
shutdown
|
|
812
|
+
shutdown,
|
|
813
|
+
restart(input) {
|
|
814
|
+
const queued = restarting.then(() => runRestart(input));
|
|
815
|
+
restarting = queued.then(() => {
|
|
816
|
+
return;
|
|
817
|
+
}, () => {
|
|
818
|
+
return;
|
|
819
|
+
});
|
|
820
|
+
return queued;
|
|
821
|
+
}
|
|
718
822
|
};
|
|
719
823
|
}
|
|
720
824
|
|
|
721
|
-
export { ApplicationShutdownOptionsSchema, ApplicationShutdownBudgetSchema, ApplicationAdmissionError, createApplication };
|
|
825
|
+
export { ApplicationShutdownOptionsSchema, ApplicationShutdownBudgetSchema, ApplicationRestartInputSchema, ApplicationRestartOutcomeSchema, ApplicationRestartResultSchema, ApplicationAdmissionError, createApplication };
|
|
@@ -272,6 +272,22 @@ function mergeMeta(contractMeta, endpointMeta) {
|
|
|
272
272
|
import { z as z2 } from "zod";
|
|
273
273
|
// src/contract/pagination.ts
|
|
274
274
|
import { z as z3 } from "zod";
|
|
275
|
+
|
|
276
|
+
// src/internal/base64url.ts
|
|
277
|
+
function bytesToBase64Url(bytes) {
|
|
278
|
+
let binary = "";
|
|
279
|
+
for (const byte of bytes)
|
|
280
|
+
binary += String.fromCharCode(byte);
|
|
281
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
282
|
+
}
|
|
283
|
+
function base64UrlToBytes(segment) {
|
|
284
|
+
if (!/^[A-Za-z0-9_-]*$/.test(segment) || segment.length % 4 === 1) {
|
|
285
|
+
throw new Error("invalid base64url segment");
|
|
286
|
+
}
|
|
287
|
+
const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
288
|
+
const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
|
|
289
|
+
return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
290
|
+
}
|
|
275
291
|
// src/contract/stream.ts
|
|
276
292
|
import { z as z4 } from "zod";
|
|
277
293
|
var DEFAULT_CONTRACT_STREAM_FRAME_BYTES = 256 * 1024;
|
|
@@ -288,4 +304,4 @@ var ContractStreamFrameSchema = z4.discriminatedUnion("type", [
|
|
|
288
304
|
}).strict(),
|
|
289
305
|
z4.object({ type: z4.literal("end") }).strict()
|
|
290
306
|
]);
|
|
291
|
-
export { joinRoutePath, parseTrailingWildcard, isUnsafeKey, safeJsonParse, defineContract, mergeMeta, DEFAULT_CONTRACT_STREAM_FRAME_BYTES, ContractStreamFrameSchema };
|
|
307
|
+
export { joinRoutePath, parseTrailingWildcard, isUnsafeKey, safeJsonParse, defineContract, mergeMeta, bytesToBase64Url, base64UrlToBytes, DEFAULT_CONTRACT_STREAM_FRAME_BYTES, ContractStreamFrameSchema };
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* One answer to "may this happen?", from one answerer.
|
|
4
|
+
*
|
|
5
|
+
* Three outcomes and no fourth, because the third is the one that gets left out
|
|
6
|
+
* and then invented: `defer` means *not my call*, which is a different statement
|
|
7
|
+
* from `allow`. Collapsing them makes every answerer that had no opinion into an
|
|
8
|
+
* answerer that approved, and nothing in the result says which happened.
|
|
9
|
+
*
|
|
10
|
+
* `deny` carries its reason as a required field. A refusal without one reaches a
|
|
11
|
+
* human as "denied" and sends them to read the policy source to find out what
|
|
12
|
+
* they did, which is the moment a policy engine stops being worth having.
|
|
13
|
+
*
|
|
14
|
+
* Browser-safe and shared on purpose: an event listener voting on a topic
|
|
15
|
+
* (`stitchkit/live`) and a policy in an ordered pipeline
|
|
16
|
+
* (`stitchkit/application`) are different mechanisms answering the same
|
|
17
|
+
* question, and one question with two vocabularies is a codebase where `grep`
|
|
18
|
+
* finds half the answerers.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PolicyDecisionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
21
|
+
outcome: z.ZodLiteral<"allow">;
|
|
22
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
23
|
+
outcome: z.ZodLiteral<"deny">;
|
|
24
|
+
reason: z.ZodString;
|
|
25
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
26
|
+
outcome: z.ZodLiteral<"defer">;
|
|
27
|
+
}, z.core.$strict>], "outcome">;
|
|
28
|
+
export type PolicyDecision = z.infer<typeof PolicyDecisionSchema>;
|
|
29
|
+
/** What is concluded when nobody claimed the question — see each mechanism for which it uses. */
|
|
30
|
+
export type UndecidedOutcome = 'allow' | 'deny';
|
|
31
|
+
//# sourceMappingURL=decision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/internal/decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;+BAI/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,iGAAiG;AACjG,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,CAAC"}
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable identity for a call's arguments — one implementation, two callers.
|
|
3
|
+
*
|
|
4
|
+
* Two callers need the same thing for different reasons. The MCP round keys a
|
|
5
|
+
* tool invocation by `(operation, arguments)` so a retry is recognised as the
|
|
6
|
+
* same round; a watched read keys its shared source by `(operation, arguments)`
|
|
7
|
+
* so two browsers asking the same question are one read on the server. If those
|
|
8
|
+
* two computed the key differently, the same arguments would be the same round
|
|
9
|
+
* and two different watches — and nothing would say so.
|
|
10
|
+
*
|
|
11
|
+
* The stability that matters is key order: `{a:1,b:2}` and `{b:2,a:1}` are the
|
|
12
|
+
* same arguments and `JSON.stringify` disagrees, so a naive key silently splits
|
|
13
|
+
* one shared source into two whenever a caller builds its object in a different
|
|
14
|
+
* order. That is not a hypothetical: object literal order follows the source
|
|
15
|
+
* that wrote it, and two components asking the same question rarely share one.
|
|
16
|
+
*
|
|
17
|
+
* ## Why this is not SHA-256
|
|
18
|
+
*
|
|
19
|
+
* It was, and that made every watched read fail on an ordinary intranet. Web
|
|
20
|
+
* Crypto — `crypto.subtle` — exists only in a **secure context**, so a page
|
|
21
|
+
* served over plain HTTP from a LAN name has no `crypto.subtle` at all and the
|
|
22
|
+
* first digest throws `Cannot read properties of undefined`. `localhost` is
|
|
23
|
+
* secure by definition, which is exactly why nothing caught it until a browser
|
|
24
|
+
* opened the app by its name.
|
|
25
|
+
*
|
|
26
|
+
* The fix is not a fallback, it is admitting what this is for: **identity, not
|
|
27
|
+
* secrecy**. Nothing here resists an adversary — an attacker who can choose your
|
|
28
|
+
* watch arguments can already ask the question directly. So the requirement is
|
|
29
|
+
* distribution and determinism, both ends agreeing, and no ambient capability;
|
|
30
|
+
* a cryptographic hash bought none of that and cost the entire non-secure web.
|
|
31
|
+
*
|
|
32
|
+
* Being synchronous is the second thing it buys. A promise for a key made the
|
|
33
|
+
* first subscription of a question asynchronous, which meant a component could
|
|
34
|
+
* not be handed a retained value in the same turn it subscribed.
|
|
35
|
+
*/
|
|
1
36
|
/**
|
|
2
37
|
* The value with every object's keys sorted, recursively — arrays keep their
|
|
3
38
|
* order, because in an array order *is* the value.
|
|
@@ -7,12 +42,11 @@
|
|
|
7
42
|
*/
|
|
8
43
|
export declare function stableValue(value: unknown): unknown;
|
|
9
44
|
/**
|
|
10
|
-
* A bounded, order-independent
|
|
45
|
+
* A bounded, order-independent identity for a call's arguments.
|
|
11
46
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* large subscription key repeated in every frame.
|
|
47
|
+
* 128 bits as 32 lowercase hex characters. Deterministic across runtimes and
|
|
48
|
+
* across both ends of a socket, synchronous, and dependent on no ambient
|
|
49
|
+
* capability — see the module header for why that last one is the whole point.
|
|
16
50
|
*
|
|
17
51
|
* What it cannot do, stated because the limit is easy to walk into: values
|
|
18
52
|
* `JSON.stringify` drops or transforms — `undefined` members, a `Date`, a `Map`,
|
|
@@ -20,5 +54,5 @@ export declare function stableValue(value: unknown): unknown;
|
|
|
20
54
|
* Contract arguments are parsed JSON, so this is the argument shape by
|
|
21
55
|
* construction; a caller digesting something else has to say what it means.
|
|
22
56
|
*/
|
|
23
|
-
export declare function argumentsDigest(args: Record<string, unknown>):
|
|
57
|
+
export declare function argumentsDigest(args: Record<string, unknown>): string;
|
|
24
58
|
//# sourceMappingURL=stable-digest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stable-digest.d.ts","sourceRoot":"","sources":["../../src/internal/stable-digest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stable-digest.d.ts","sourceRoot":"","sources":["../../src/internal/stable-digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQnD;AAyCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAGrE"}
|