@loomwork/middleware 0.0.0-stage → 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 +202 -0
- package/NOTICE +8 -0
- package/README.md +82 -2
- package/dist/claimCheck.d.ts +179 -0
- package/dist/claimCheck.d.ts.map +1 -0
- package/dist/claimCheck.js +241 -0
- package/dist/claimCheck.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/killSwitchRegistry.d.ts +74 -0
- package/dist/killSwitchRegistry.d.ts.map +1 -0
- package/dist/killSwitchRegistry.js +108 -0
- package/dist/killSwitchRegistry.js.map +1 -0
- package/dist/metering.d.ts +81 -0
- package/dist/metering.d.ts.map +1 -0
- package/dist/metering.js +153 -0
- package/dist/metering.js.map +1 -0
- package/dist/nestedBudget.d.ts +60 -0
- package/dist/nestedBudget.d.ts.map +1 -0
- package/dist/nestedBudget.js +134 -0
- package/dist/nestedBudget.js.map +1 -0
- package/dist/profile.d.ts +38 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +70 -0
- package/dist/profile.js.map +1 -0
- package/dist/rateLimit.d.ts +40 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +83 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/redaction.d.ts +66 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +141 -0
- package/dist/redaction.js.map +1 -0
- package/dist/retry.d.ts +50 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +98 -0
- package/dist/retry.js.map +1 -0
- package/dist/safety.d.ts +93 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +152 -0
- package/dist/safety.js.map +1 -0
- package/dist/streamTokenBudget.d.ts +58 -0
- package/dist/streamTokenBudget.d.ts.map +1 -0
- package/dist/streamTokenBudget.js +47 -0
- package/dist/streamTokenBudget.js.map +1 -0
- package/package.json +55 -3
- package/src/claimCheck.ts +410 -0
- package/src/index.ts +79 -0
- package/src/killSwitchRegistry.ts +168 -0
- package/src/metering.ts +226 -0
- package/src/nestedBudget.ts +200 -0
- package/src/profile.ts +86 -0
- package/src/rateLimit.ts +126 -0
- package/src/redaction.ts +184 -0
- package/src/retry.ts +133 -0
- package/src/safety.ts +235 -0
- package/src/streamTokenBudget.ts +98 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { StepDefinition } from "@loomwork/core";
|
|
2
|
+
import type { EffectMiddleware } from "@loomwork/effect-runtime";
|
|
3
|
+
/** The deterministic reference that stands in for a secret in step I/O and the log. */
|
|
4
|
+
export interface SecretRef {
|
|
5
|
+
readonly __loomSecret: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function secretRef(id: string): SecretRef;
|
|
8
|
+
export declare function isSecretRef(value: unknown): value is SecretRef;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves secret references to their real values (read) and, for output
|
|
11
|
+
* redaction, stores a value under a reference (write). Back it with a vault /
|
|
12
|
+
* KMS / secret manager in production; the resolver is the boundary the plaintext
|
|
13
|
+
* lives behind. `InMemorySecretResolver` is for tests.
|
|
14
|
+
*/
|
|
15
|
+
export interface SecretResolver {
|
|
16
|
+
resolve(id: string): Promise<unknown> | unknown;
|
|
17
|
+
/** Optional — required only when `redactOutput` mints references from results. */
|
|
18
|
+
store?(id: string, value: unknown): Promise<void> | void;
|
|
19
|
+
}
|
|
20
|
+
export declare class InMemorySecretResolver implements SecretResolver {
|
|
21
|
+
private readonly secrets;
|
|
22
|
+
constructor(secrets?: Map<string, unknown>);
|
|
23
|
+
resolve(id: string): unknown;
|
|
24
|
+
store(id: string, value: unknown): void;
|
|
25
|
+
}
|
|
26
|
+
export interface RedactionOptions {
|
|
27
|
+
resolver: SecretResolver;
|
|
28
|
+
/**
|
|
29
|
+
* Output dot-paths (per effect) whose values must NOT enter the log: the value
|
|
30
|
+
* is stored in the resolver under a deterministic reference
|
|
31
|
+
* (`executionId:stepId:path`) and REPLACED by that reference in the result, so
|
|
32
|
+
* STEP_COMPLETED logs the reference. Requires `resolver.store`. Omit to only
|
|
33
|
+
* resolve input references (the common case).
|
|
34
|
+
*/
|
|
35
|
+
redactOutput?: (info: {
|
|
36
|
+
stepId: string;
|
|
37
|
+
}) => readonly string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* K10.3 — redaction middleware. Runs on the effect path (like metering/safety):
|
|
41
|
+
* - **input**: resolves every `SecretRef` in the effect input to its real value
|
|
42
|
+
* before the tool runs, WITHOUT touching the logged input (STEP_STARTED keeps
|
|
43
|
+
* the reference — the secret never enters the log).
|
|
44
|
+
* - **output** (optional): replaces marked result fields with deterministic
|
|
45
|
+
* references (storing the value in the resolver), so STEP_COMPLETED logs the
|
|
46
|
+
* reference, not the secret.
|
|
47
|
+
*
|
|
48
|
+
* Unwired (no middleware), effects run byte-identically; with it but no refs in
|
|
49
|
+
* play, it is a deep-walk pass-through (G2).
|
|
50
|
+
*/
|
|
51
|
+
export declare function redactionMiddleware(options: RedactionOptions): EffectMiddleware;
|
|
52
|
+
/**
|
|
53
|
+
* Precompute a `stepId → secret output paths` lookup from step definitions, for O(1) access
|
|
54
|
+
* in the per-effect redactOutput callback (a Map, not a per-call schema walk). Steps with no
|
|
55
|
+
* `secret()`-marked output field are omitted, so an all-unmarked app yields an empty Map and
|
|
56
|
+
* the redaction pass stays a no-op (G2).
|
|
57
|
+
*/
|
|
58
|
+
export declare function secretOutputPathsByStep(steps: readonly Pick<StepDefinition, "id" | "outputSchema">[]): Map<string, readonly string[]>;
|
|
59
|
+
/**
|
|
60
|
+
* A {@link RedactionOptions.redactOutput} callback derived from `secret()` marks — pass the
|
|
61
|
+
* result of {@link secretOutputPathsByStep} (or let `defineConfig({ secrets })` wire it).
|
|
62
|
+
*/
|
|
63
|
+
export declare function redactOutputFrom(byStep: Map<string, readonly string[]>): (info: {
|
|
64
|
+
stepId: string;
|
|
65
|
+
}) => readonly string[];
|
|
66
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAiB,gBAAgB,EAAc,MAAM,0BAA0B,CAAC;AAc5F,uFAAuF;AACvF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAE/C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAM9D;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAChD,kFAAkF;IAClF,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,qBAAa,sBAAuB,YAAW,cAAc;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa;IACtE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI5B,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;CAGxC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,MAAM,EAAE,CAAC;CAChE;AA6BD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAkC/E;AAcD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAGrC,KAAK,EAAE,SAAS,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,cAAc,CAAC,EAAE,GAC5D,GAAG,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAQhC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,GACrC,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,KAAK,SAAS,MAAM,EAAE,CAEjD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemorySecretResolver = void 0;
|
|
4
|
+
exports.secretRef = secretRef;
|
|
5
|
+
exports.isSecretRef = isSecretRef;
|
|
6
|
+
exports.redactionMiddleware = redactionMiddleware;
|
|
7
|
+
exports.secretOutputPathsByStep = secretOutputPathsByStep;
|
|
8
|
+
exports.redactOutputFrom = redactOutputFrom;
|
|
9
|
+
const classify_1 = require("@loomwork/classify");
|
|
10
|
+
function secretRef(id) {
|
|
11
|
+
return { __loomSecret: id };
|
|
12
|
+
}
|
|
13
|
+
function isSecretRef(value) {
|
|
14
|
+
return (typeof value === "object" &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
typeof value.__loomSecret === "string");
|
|
17
|
+
}
|
|
18
|
+
class InMemorySecretResolver {
|
|
19
|
+
secrets;
|
|
20
|
+
constructor(secrets = new Map()) {
|
|
21
|
+
this.secrets = secrets;
|
|
22
|
+
}
|
|
23
|
+
resolve(id) {
|
|
24
|
+
if (!this.secrets.has(id))
|
|
25
|
+
throw new Error(`no secret registered for reference "${id}"`);
|
|
26
|
+
return this.secrets.get(id);
|
|
27
|
+
}
|
|
28
|
+
store(id, value) {
|
|
29
|
+
this.secrets.set(id, value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.InMemorySecretResolver = InMemorySecretResolver;
|
|
33
|
+
async function deepResolve(value, resolver) {
|
|
34
|
+
if (isSecretRef(value))
|
|
35
|
+
return resolver.resolve(value.__loomSecret);
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return Promise.all(value.map((v) => deepResolve(v, resolver)));
|
|
38
|
+
if (typeof value === "object" && value !== null) {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [k, v] of Object.entries(value))
|
|
41
|
+
out[k] = await deepResolve(v, resolver);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function getPath(root, path) {
|
|
47
|
+
const segs = path.split(".");
|
|
48
|
+
let cursor = root;
|
|
49
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
50
|
+
const next = cursor?.[segs[i]];
|
|
51
|
+
cursor =
|
|
52
|
+
typeof next === "object" && next !== null ? next : undefined;
|
|
53
|
+
if (cursor === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const leaf = segs[segs.length - 1];
|
|
57
|
+
return cursor !== undefined && Object.hasOwn(cursor, leaf) ? { parent: cursor, leaf } : undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* K10.3 — redaction middleware. Runs on the effect path (like metering/safety):
|
|
61
|
+
* - **input**: resolves every `SecretRef` in the effect input to its real value
|
|
62
|
+
* before the tool runs, WITHOUT touching the logged input (STEP_STARTED keeps
|
|
63
|
+
* the reference — the secret never enters the log).
|
|
64
|
+
* - **output** (optional): replaces marked result fields with deterministic
|
|
65
|
+
* references (storing the value in the resolver), so STEP_COMPLETED logs the
|
|
66
|
+
* reference, not the secret.
|
|
67
|
+
*
|
|
68
|
+
* Unwired (no middleware), effects run byte-identically; with it but no refs in
|
|
69
|
+
* play, it is a deep-walk pass-through (G2).
|
|
70
|
+
*/
|
|
71
|
+
function redactionMiddleware(options) {
|
|
72
|
+
const mw = async (ctx, input, next) => {
|
|
73
|
+
// Input phase — resolve references into the value the tool actually sees,
|
|
74
|
+
// WITHOUT touching the logged input (STEP_STARTED keeps the reference).
|
|
75
|
+
const resolvedInput = await deepResolve(input, options.resolver);
|
|
76
|
+
const result = await next(resolvedInput);
|
|
77
|
+
// Output phase — redact marked result fields into references.
|
|
78
|
+
const paths = options.redactOutput?.({ stepId: ctx.stepId }) ?? [];
|
|
79
|
+
if (paths.length === 0)
|
|
80
|
+
return result;
|
|
81
|
+
if (options.resolver.store === undefined) {
|
|
82
|
+
throw new Error("redactOutput requires a SecretResolver with store()");
|
|
83
|
+
}
|
|
84
|
+
const redacted = structuredClone(result);
|
|
85
|
+
for (const path of paths) {
|
|
86
|
+
const hit = getPath(redacted, path);
|
|
87
|
+
if (hit === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
// A transient call stamped as an execution shares (executionId, stepId) with its
|
|
90
|
+
// siblings; callKey keeps their secret refs distinct (undefined for durable steps).
|
|
91
|
+
const refId = `${ctx.executionId}:${ctx.stepId}${ctx.callKey !== undefined ? `:${ctx.callKey}` : ""}:${path}`;
|
|
92
|
+
await options.resolver.store(refId, hit.parent[hit.leaf]);
|
|
93
|
+
hit.parent[hit.leaf] = secretRef(refId); // the LOG gets the reference
|
|
94
|
+
}
|
|
95
|
+
return redacted;
|
|
96
|
+
};
|
|
97
|
+
// ADR 0119 — output redaction REWRITES the result after `next()`; a streamed delta
|
|
98
|
+
// bypasses that masking, so declare it: the runtime then refuses live deltas (fail
|
|
99
|
+
// closed) rather than stream the secret past the mask. Input-only resolution does not
|
|
100
|
+
// touch output, so it stays streamable.
|
|
101
|
+
return (options.redactOutput !== undefined ? Object.assign(mw, { inspectsOutput: true }) : mw);
|
|
102
|
+
}
|
|
103
|
+
// ── K10-R2 — derive output redaction from `secret()` marks ────────────────────
|
|
104
|
+
//
|
|
105
|
+
// Instead of hand-authoring a `redactOutput: ({stepId}) => paths` callback, mark the
|
|
106
|
+
// field once — `apiToken: secret(z.string())` — and derive it. A step's output fields
|
|
107
|
+
// classified `"secret"` are the ones that must never be persisted; each becomes a
|
|
108
|
+
// redactOutput path (relative to the effect RESULT, which is why there is no `output.`
|
|
109
|
+
// prefix — the crypto map operates on the event payload, redaction on the raw result).
|
|
110
|
+
//
|
|
111
|
+
// This is the low-risk half of K10-R2: it drives the EXISTING effect-path redaction seam
|
|
112
|
+
// off the R1 taxonomy, with NO kernel change. Automatic secret substitution before
|
|
113
|
+
// STEP_STARTED in the hot path remains deferred (its determinism stakes are higher).
|
|
114
|
+
/**
|
|
115
|
+
* Precompute a `stepId → secret output paths` lookup from step definitions, for O(1) access
|
|
116
|
+
* in the per-effect redactOutput callback (a Map, not a per-call schema walk). Steps with no
|
|
117
|
+
* `secret()`-marked output field are omitted, so an all-unmarked app yields an empty Map and
|
|
118
|
+
* the redaction pass stays a no-op (G2).
|
|
119
|
+
*/
|
|
120
|
+
function secretOutputPathsByStep(
|
|
121
|
+
// Only id + output schema are read; Pick drops the invariant handler field so concrete
|
|
122
|
+
// `StepDefinition<In, Out>` values pass without a cast.
|
|
123
|
+
steps) {
|
|
124
|
+
const byStep = new Map();
|
|
125
|
+
for (const step of steps) {
|
|
126
|
+
if (step.outputSchema === undefined)
|
|
127
|
+
continue;
|
|
128
|
+
const paths = (0, classify_1.pathsOfClass)(step.outputSchema, "secret");
|
|
129
|
+
if (paths.length > 0)
|
|
130
|
+
byStep.set(step.id, paths);
|
|
131
|
+
}
|
|
132
|
+
return byStep;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A {@link RedactionOptions.redactOutput} callback derived from `secret()` marks — pass the
|
|
136
|
+
* result of {@link secretOutputPathsByStep} (or let `defineConfig({ secrets })` wire it).
|
|
137
|
+
*/
|
|
138
|
+
function redactOutputFrom(byStep) {
|
|
139
|
+
return ({ stepId }) => byStep.get(stepId) ?? [];
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { EffectContext, EffectMiddleware } from "@loomwork/effect-runtime";
|
|
2
|
+
export interface RetryOptions {
|
|
3
|
+
/** Max retries after the first attempt. Default 2 (⇒ up to 3 tries). */
|
|
4
|
+
maxRetries?: number;
|
|
5
|
+
/** Delay before the first retry, in ms. Default 100. */
|
|
6
|
+
baseDelayMs?: number;
|
|
7
|
+
/** Exponential growth factor between retries. Default 2. */
|
|
8
|
+
factor?: number;
|
|
9
|
+
/** Upper bound on any single delay, in ms. Default 30_000. */
|
|
10
|
+
maxDelayMs?: number;
|
|
11
|
+
/** Spread delays deterministically to avoid thundering herds. Default true. */
|
|
12
|
+
jitter?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Decide whether a given error is retryable. Default: all (non-control) errors
|
|
15
|
+
* EXCEPT `TypedFailure` (DE-4) — a declared domain outcome is not transient, so
|
|
16
|
+
* retrying it by default would just burn attempts on a deterministic result — and any
|
|
17
|
+
* error that declares `retryable: false` (R9's `KillSwitchEngagedError`,
|
|
18
|
+
* `RateLimitExceededError`, `SpawnLimitExceededError`, `SafetyViolationError`).
|
|
19
|
+
* An explicit `isRetryable` retains full control (retrying a TypedFailure
|
|
20
|
+
* in-memory is legal: no durable event has been emitted yet).
|
|
21
|
+
*/
|
|
22
|
+
isRetryable?: (err: unknown) => boolean;
|
|
23
|
+
/** Observability hook fired before each backoff wait. */
|
|
24
|
+
onRetry?: (info: {
|
|
25
|
+
ctx: EffectContext;
|
|
26
|
+
attempt: number;
|
|
27
|
+
delayMs: number;
|
|
28
|
+
err: unknown;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** Injectable sleep (for tests). Must reject if `signal` aborts. */
|
|
31
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Retry-with-backoff middleware — fills the gap where effect failures are
|
|
35
|
+
* terminal today. Wraps a single `EffectRuntime.execute`; on a retryable error
|
|
36
|
+
* it waits (exponential backoff + deterministic jitter) and re-invokes the
|
|
37
|
+
* effect, up to `maxRetries` times.
|
|
38
|
+
*
|
|
39
|
+
* Correctness rules:
|
|
40
|
+
* - **Never retries `SuspendError`** (a control-flow signal, not a failure) or a
|
|
41
|
+
* **cancelled** execution (`ctx.signal.aborted`) — both rethrow immediately.
|
|
42
|
+
* - Retries happen **in-memory within one execute call** and emit no durable
|
|
43
|
+
* events, so the STEP_STARTED/COMPLETED envelope and the event log stay
|
|
44
|
+
* deterministic; use `onRetry` for logging/metrics. Exhaustion rethrows the
|
|
45
|
+
* last error into the existing `STEP_FAILED` path — behavior-preserving.
|
|
46
|
+
* - Jitter is a deterministic hash of `executionId:stepId:attempt` (no
|
|
47
|
+
* `Math.random`), so replay/behaviour stays stable.
|
|
48
|
+
*/
|
|
49
|
+
export declare function retryMiddleware(options?: RetryOptions): EffectMiddleware;
|
|
50
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEhF,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACxC,yDAAyD;IACzD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACjG,oEAAoE;IACpE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,YAAiB,GAAG,gBAAgB,CAmC5E"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retryMiddleware = retryMiddleware;
|
|
4
|
+
const core_1 = require("@loomwork/core");
|
|
5
|
+
/**
|
|
6
|
+
* Retry-with-backoff middleware — fills the gap where effect failures are
|
|
7
|
+
* terminal today. Wraps a single `EffectRuntime.execute`; on a retryable error
|
|
8
|
+
* it waits (exponential backoff + deterministic jitter) and re-invokes the
|
|
9
|
+
* effect, up to `maxRetries` times.
|
|
10
|
+
*
|
|
11
|
+
* Correctness rules:
|
|
12
|
+
* - **Never retries `SuspendError`** (a control-flow signal, not a failure) or a
|
|
13
|
+
* **cancelled** execution (`ctx.signal.aborted`) — both rethrow immediately.
|
|
14
|
+
* - Retries happen **in-memory within one execute call** and emit no durable
|
|
15
|
+
* events, so the STEP_STARTED/COMPLETED envelope and the event log stay
|
|
16
|
+
* deterministic; use `onRetry` for logging/metrics. Exhaustion rethrows the
|
|
17
|
+
* last error into the existing `STEP_FAILED` path — behavior-preserving.
|
|
18
|
+
* - Jitter is a deterministic hash of `executionId:stepId:attempt` (no
|
|
19
|
+
* `Math.random`), so replay/behaviour stays stable.
|
|
20
|
+
*/
|
|
21
|
+
function retryMiddleware(options = {}) {
|
|
22
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
23
|
+
const baseDelayMs = options.baseDelayMs ?? 100;
|
|
24
|
+
const factor = options.factor ?? 2;
|
|
25
|
+
const maxDelayMs = options.maxDelayMs ?? 30_000;
|
|
26
|
+
const useJitter = options.jitter ?? true;
|
|
27
|
+
const isRetryable = options.isRetryable ??
|
|
28
|
+
((err) => !(err instanceof core_1.TypedFailure) &&
|
|
29
|
+
// R9 — a control-plane refusal (kill switch, rate limit, spawn cap) marks itself
|
|
30
|
+
// `retryable: false`: it is a decision, and retrying repeats it.
|
|
31
|
+
err?.retryable !== false);
|
|
32
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
33
|
+
return async (ctx, _input, next) => {
|
|
34
|
+
for (let attemptIndex = 0;; attemptIndex++) {
|
|
35
|
+
try {
|
|
36
|
+
return await next();
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
// Control-flow + cancellation are never "failures" to retry.
|
|
40
|
+
if (err instanceof core_1.SuspendError)
|
|
41
|
+
throw err;
|
|
42
|
+
if (ctx.signal?.aborted === true)
|
|
43
|
+
throw err;
|
|
44
|
+
if (attemptIndex >= maxRetries || !isRetryable(err))
|
|
45
|
+
throw err;
|
|
46
|
+
const delayMs = computeDelay(attemptIndex, { baseDelayMs, factor, maxDelayMs, useJitter }, ctx);
|
|
47
|
+
options.onRetry?.({ ctx, attempt: attemptIndex + 1, delayMs, err });
|
|
48
|
+
await sleep(delayMs, ctx.signal);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function computeDelay(attemptIndex, cfg, ctx) {
|
|
54
|
+
const base = Math.min(cfg.maxDelayMs, cfg.baseDelayMs * cfg.factor ** attemptIndex);
|
|
55
|
+
if (!cfg.useJitter)
|
|
56
|
+
return Math.round(base);
|
|
57
|
+
// Deterministic "full jitter" in [base/2, base] — stable across replay.
|
|
58
|
+
const frac = hashFraction(`${ctx.executionId}:${ctx.stepId}:${attemptIndex}`);
|
|
59
|
+
return Math.round(base / 2 + frac * (base / 2));
|
|
60
|
+
}
|
|
61
|
+
/** FNV-1a → [0, 1). Deterministic; no Math.random. */
|
|
62
|
+
function hashFraction(key) {
|
|
63
|
+
let h = 0x811c9dc5;
|
|
64
|
+
for (let i = 0; i < key.length; i++) {
|
|
65
|
+
h ^= key.charCodeAt(i);
|
|
66
|
+
h = Math.imul(h, 0x01000193);
|
|
67
|
+
}
|
|
68
|
+
// >>> 0 to unsigned, scale into [0,1)
|
|
69
|
+
return (h >>> 0) / 0xffffffff;
|
|
70
|
+
}
|
|
71
|
+
function defaultSleep(ms, signal) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
if (signal?.aborted === true) {
|
|
74
|
+
reject(abortError(signal));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
cleanup();
|
|
79
|
+
resolve();
|
|
80
|
+
}, ms);
|
|
81
|
+
const onAbort = () => {
|
|
82
|
+
cleanup();
|
|
83
|
+
reject(abortError(signal));
|
|
84
|
+
};
|
|
85
|
+
const cleanup = () => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
signal?.removeEventListener("abort", onAbort);
|
|
88
|
+
};
|
|
89
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function abortError(signal) {
|
|
93
|
+
const reason = signal?.reason;
|
|
94
|
+
if (reason instanceof Error)
|
|
95
|
+
return reason;
|
|
96
|
+
return new Error("aborted");
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
|
package/dist/safety.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { EffectMiddleware } from "@loomwork/effect-runtime";
|
|
2
|
+
export type SafetyPhase = "input" | "output" | "delta";
|
|
3
|
+
export interface SafetyCheckInfo {
|
|
4
|
+
phase: SafetyPhase;
|
|
5
|
+
stepId: string;
|
|
6
|
+
/** The effect input ("input" and "output" phases; `undefined` on "delta"). */
|
|
7
|
+
input: unknown;
|
|
8
|
+
/** The effect result — present only on the "output" phase. */
|
|
9
|
+
result?: unknown;
|
|
10
|
+
/**
|
|
11
|
+
* The RUNNING text of a streamed call — present only on the "delta" phase. It is the
|
|
12
|
+
* last {@link SafetyOptions.deltaWindow} characters received so far INCLUDING the delta
|
|
13
|
+
* under test, so a pattern split across deltas is seen whole (up to the window).
|
|
14
|
+
*/
|
|
15
|
+
text?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SafetyVerdict {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SafetyValidator {
|
|
22
|
+
id: string;
|
|
23
|
+
phase: SafetyPhase;
|
|
24
|
+
category?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Return `{ ok: false }` to block. Should be pure/deterministic. A `"delta"`-phase
|
|
27
|
+
* validator receives `info.text` (the running streamed text) and runs ONLY on a
|
|
28
|
+
* streamed call, severing the stream before the offending delta is delivered.
|
|
29
|
+
*/
|
|
30
|
+
check(info: SafetyCheckInfo): SafetyVerdict;
|
|
31
|
+
/**
|
|
32
|
+
* ADR 0119 — the DELTA FORM of an `"output"` validator: the same policy applied to the
|
|
33
|
+
* running text of a streamed call (`info.phase === "delta"`, `info.text`). An output
|
|
34
|
+
* validator WITHOUT one cannot see a stream, so when any is configured the runtime
|
|
35
|
+
* REFUSES streaming to every delta sink — per-call and configured (`EffectMiddleware.inspectsOutput`) rather than stream
|
|
36
|
+
* text past it. The output form still runs on the final result either way.
|
|
37
|
+
*/
|
|
38
|
+
checkDelta?(info: SafetyCheckInfo): SafetyVerdict;
|
|
39
|
+
}
|
|
40
|
+
export interface SafetyOptions {
|
|
41
|
+
validators: readonly SafetyValidator[];
|
|
42
|
+
/**
|
|
43
|
+
* ADR 0119 — how many trailing characters of a streamed call's running text a delta
|
|
44
|
+
* check sees (default 4096). It bounds per-delta cost (the check is O(window), not
|
|
45
|
+
* O(text so far)). A pattern that SPANS more than the window across deltas can evade
|
|
46
|
+
* the delta check; the output form still sees the whole result, but by then the
|
|
47
|
+
* caller's live tail has already received it. Text BEFORE a match completes is also
|
|
48
|
+
* delivered (a card number is severed at its last digit, not its first) — a delta
|
|
49
|
+
* form narrows what a live tail can show, it does not make a stream as safe as a
|
|
50
|
+
* buffered result.
|
|
51
|
+
*/
|
|
52
|
+
deltaWindow?: number;
|
|
53
|
+
}
|
|
54
|
+
export declare class SafetyViolationError extends Error {
|
|
55
|
+
readonly validatorId: string;
|
|
56
|
+
readonly category: string | undefined;
|
|
57
|
+
readonly reason: string;
|
|
58
|
+
/**
|
|
59
|
+
* Honoured by `retryMiddleware`'s default classifier: a violation is a policy decision,
|
|
60
|
+
* not a transient fault. Retrying re-calls the provider — for a severed stream (ADR 0119)
|
|
61
|
+
* that is a second paid call whose deltas a live tail already started showing. An app
|
|
62
|
+
* that wants "regenerate on violation" opts in with an explicit `isRetryable`.
|
|
63
|
+
*/
|
|
64
|
+
readonly retryable = false;
|
|
65
|
+
constructor(validatorId: string, reason: string, category?: string);
|
|
66
|
+
}
|
|
67
|
+
export declare function safetyMiddleware(options: SafetyOptions): EffectMiddleware;
|
|
68
|
+
/** Block if the checked value (input or result) matches any pattern (jailbreak/policy). */
|
|
69
|
+
export declare function denyPatterns(patterns: readonly RegExp[], opts?: {
|
|
70
|
+
id?: string;
|
|
71
|
+
phase?: SafetyPhase;
|
|
72
|
+
category?: string;
|
|
73
|
+
}): SafetyValidator;
|
|
74
|
+
/** Block if the checked value contains obvious PII (email, US SSN, card-like digits). */
|
|
75
|
+
export declare function piiValidator(opts?: {
|
|
76
|
+
id?: string;
|
|
77
|
+
phase?: SafetyPhase;
|
|
78
|
+
}): SafetyValidator;
|
|
79
|
+
/** A minimal Zod-compatible shape — anything with a `safeParse`. */
|
|
80
|
+
export interface SchemaLike {
|
|
81
|
+
safeParse(value: unknown): {
|
|
82
|
+
success: boolean;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Enforce an effect's output schema at runtime (schemas are defined-but-unchecked today). */
|
|
86
|
+
export declare function schemaValidator(schema: SchemaLike, opts?: {
|
|
87
|
+
id?: string;
|
|
88
|
+
}): SafetyValidator;
|
|
89
|
+
/** A real kill-switch: block every effect while `isTripped()` returns true (distinct from K4). */
|
|
90
|
+
export declare function killSwitch(isTripped: () => boolean, opts?: {
|
|
91
|
+
id?: string;
|
|
92
|
+
}): SafetyValidator;
|
|
93
|
+
//# sourceMappingURL=safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAmBhF,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,CAAC;IAC5C;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,CAAC;CACnD;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IACvC;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,SAAS;gBACf,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;CAOnE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,gBAAgB,CA2DzE;AAQD,2FAA2F;AAC3F,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACjE,eAAe,CAuBjB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAO,GAAG,eAAe,CAU7F;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CACjD;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAW/F;AAED,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,OAAO,EAAE,IAAI,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAShG"}
|
package/dist/safety.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SafetyViolationError = void 0;
|
|
4
|
+
exports.safetyMiddleware = safetyMiddleware;
|
|
5
|
+
exports.denyPatterns = denyPatterns;
|
|
6
|
+
exports.piiValidator = piiValidator;
|
|
7
|
+
exports.schemaValidator = schemaValidator;
|
|
8
|
+
exports.killSwitch = killSwitch;
|
|
9
|
+
class SafetyViolationError extends Error {
|
|
10
|
+
validatorId;
|
|
11
|
+
category;
|
|
12
|
+
reason;
|
|
13
|
+
/**
|
|
14
|
+
* Honoured by `retryMiddleware`'s default classifier: a violation is a policy decision,
|
|
15
|
+
* not a transient fault. Retrying re-calls the provider — for a severed stream (ADR 0119)
|
|
16
|
+
* that is a second paid call whose deltas a live tail already started showing. An app
|
|
17
|
+
* that wants "regenerate on violation" opts in with an explicit `isRetryable`.
|
|
18
|
+
*/
|
|
19
|
+
retryable = false;
|
|
20
|
+
constructor(validatorId, reason, category) {
|
|
21
|
+
super(`Safety violation [${validatorId}]: ${reason}`);
|
|
22
|
+
this.name = "SafetyViolationError";
|
|
23
|
+
this.validatorId = validatorId;
|
|
24
|
+
this.category = category;
|
|
25
|
+
this.reason = reason;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.SafetyViolationError = SafetyViolationError;
|
|
29
|
+
function safetyMiddleware(options) {
|
|
30
|
+
const inputValidators = options.validators.filter((v) => v.phase === "input");
|
|
31
|
+
const outputValidators = options.validators.filter((v) => v.phase === "output");
|
|
32
|
+
// The delta checks: a delta-phase validator's `check`, an output validator's `checkDelta`.
|
|
33
|
+
const deltaChecks = [];
|
|
34
|
+
for (const v of options.validators) {
|
|
35
|
+
if (v.phase === "delta")
|
|
36
|
+
deltaChecks.push({ v, run: (i) => v.check(i) });
|
|
37
|
+
else if (v.phase === "output" && v.checkDelta !== undefined) {
|
|
38
|
+
const f = v.checkDelta.bind(v);
|
|
39
|
+
deltaChecks.push({ v, run: f });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const uncoveredOutput = outputValidators.some((v) => v.checkDelta === undefined);
|
|
43
|
+
const window = Math.max(1, options.deltaWindow ?? 4096);
|
|
44
|
+
const block = (ctx, v, phase, reason) => {
|
|
45
|
+
ctx.emit("SAFETY_BLOCKED", {
|
|
46
|
+
stepId: ctx.stepId,
|
|
47
|
+
validatorId: v.id,
|
|
48
|
+
phase,
|
|
49
|
+
reason,
|
|
50
|
+
...(v.category !== undefined && { category: v.category }),
|
|
51
|
+
});
|
|
52
|
+
throw new SafetyViolationError(v.id, reason, v.category);
|
|
53
|
+
};
|
|
54
|
+
const run = (ctx, validators, info) => {
|
|
55
|
+
for (const v of validators) {
|
|
56
|
+
const verdict = v.check(info);
|
|
57
|
+
if (!verdict.ok)
|
|
58
|
+
block(ctx, v, info.phase, verdict.reason ?? "blocked");
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const mw = async (ctx, input, next) => {
|
|
62
|
+
run(ctx, inputValidators, { phase: "input", stepId: ctx.stepId, input });
|
|
63
|
+
const result = await next();
|
|
64
|
+
if (outputValidators.length > 0) {
|
|
65
|
+
run(ctx, outputValidators, { phase: "output", stepId: ctx.stepId, input, result });
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
return Object.assign(mw, {
|
|
70
|
+
// An output validator with no delta form cannot see a stream: tell the runtime so it
|
|
71
|
+
// refuses a live sink instead of streaming text past it (fail-closed).
|
|
72
|
+
...(uncoveredOutput && { inspectsOutput: true }),
|
|
73
|
+
...(deltaChecks.length > 0 && {
|
|
74
|
+
deltaGuard: (ctx) => {
|
|
75
|
+
let text = "";
|
|
76
|
+
return (delta) => {
|
|
77
|
+
text = (text + delta).slice(-window);
|
|
78
|
+
for (const { v, run: check } of deltaChecks) {
|
|
79
|
+
const verdict = check({ phase: "delta", stepId: ctx.stepId, input: undefined, text });
|
|
80
|
+
if (!verdict.ok)
|
|
81
|
+
block(ctx, v, "delta", verdict.reason ?? "blocked");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// ── Built-in validators ──────────────────────────────────────────────────────
|
|
89
|
+
function asText(value) {
|
|
90
|
+
return typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
91
|
+
}
|
|
92
|
+
/** Block if the checked value (input or result) matches any pattern (jailbreak/policy). */
|
|
93
|
+
function denyPatterns(patterns, opts = {}) {
|
|
94
|
+
const phase = opts.phase ?? "output";
|
|
95
|
+
const scan = (text) => {
|
|
96
|
+
for (const p of patterns) {
|
|
97
|
+
if (p.test(text))
|
|
98
|
+
return { ok: false, reason: `matched ${p}` };
|
|
99
|
+
}
|
|
100
|
+
return { ok: true };
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
id: opts.id ?? "deny-patterns",
|
|
104
|
+
phase,
|
|
105
|
+
category: opts.category ?? "policy",
|
|
106
|
+
check(info) {
|
|
107
|
+
return scan(info.phase === "delta"
|
|
108
|
+
? (info.text ?? "")
|
|
109
|
+
: asText(phase === "output" ? info.result : info.input));
|
|
110
|
+
},
|
|
111
|
+
// The delta form of the default (output) validator: the same patterns over the running
|
|
112
|
+
// text, so a default config protects a stream. An input-phase validator has none.
|
|
113
|
+
...(phase === "output" && { checkDelta: (info) => scan(info.text ?? "") }),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Block if the checked value contains obvious PII (email, US SSN, card-like digits). */
|
|
117
|
+
function piiValidator(opts = {}) {
|
|
118
|
+
const PII = [
|
|
119
|
+
/\b[\w.+-]+@[\w-]+\.[\w.-]+\b/, // email
|
|
120
|
+
/\b\d{3}-\d{2}-\d{4}\b/, // US SSN
|
|
121
|
+
/\b(?:\d[ -]?){13,16}\b/, // card-like
|
|
122
|
+
];
|
|
123
|
+
return {
|
|
124
|
+
...denyPatterns(PII, { id: opts.id ?? "pii", ...(opts.phase && { phase: opts.phase }) }),
|
|
125
|
+
category: "pii",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Enforce an effect's output schema at runtime (schemas are defined-but-unchecked today). */
|
|
129
|
+
function schemaValidator(schema, opts = {}) {
|
|
130
|
+
return {
|
|
131
|
+
id: opts.id ?? "schema",
|
|
132
|
+
phase: "output",
|
|
133
|
+
category: "schema",
|
|
134
|
+
check(info) {
|
|
135
|
+
return schema.safeParse(info.result).success
|
|
136
|
+
? { ok: true }
|
|
137
|
+
: { ok: false, reason: "output failed schema validation" };
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** A real kill-switch: block every effect while `isTripped()` returns true (distinct from K4). */
|
|
142
|
+
function killSwitch(isTripped, opts = {}) {
|
|
143
|
+
return {
|
|
144
|
+
id: opts.id ?? "kill-switch",
|
|
145
|
+
phase: "input",
|
|
146
|
+
category: "kill-switch",
|
|
147
|
+
check() {
|
|
148
|
+
return isTripped() ? { ok: false, reason: "kill-switch engaged" } : { ok: true };
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":";;;AAgGA,4CA2DC;AASD,oCA0BC;AAGD,oCAUC;AAQD,0CAWC;AAGD,gCASC;AA9JD,MAAa,oBAAqB,SAAQ,KAAK;IACpC,WAAW,CAAS;IACpB,QAAQ,CAAqB;IAC7B,MAAM,CAAS;IACxB;;;;;OAKG;IACM,SAAS,GAAG,KAAK,CAAC;IAC3B,YAAY,WAAmB,EAAE,MAAc,EAAE,QAAiB;QAChE,KAAK,CAAC,qBAAqB,WAAW,MAAM,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAlBD,oDAkBC;AAED,SAAgB,gBAAgB,CAAC,OAAsB;IACrD,MAAM,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;IAC9E,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC;IAChF,2FAA2F;IAC3F,MAAM,WAAW,GAA8E,EAAE,CAAC;IAClG,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;YAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aACpE,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC/B,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,MAAM,eAAe,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC;IAExD,MAAM,KAAK,GAAG,CAAC,GAAkB,EAAE,CAAkB,EAAE,KAAkB,EAAE,MAAc,EAAE,EAAE;QAC3F,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE;YACzB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,WAAW,EAAE,CAAC,CAAC,EAAE;YACjB,KAAK;YACL,MAAM;YACN,GAAG,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;SAC1D,CAAC,CAAC;QACH,MAAM,IAAI,oBAAoB,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,GAAkB,EAAE,UAA6B,EAAE,IAAqB,EAAQ,EAAE;QAC7F,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,CAAC,OAAO,CAAC,EAAE;gBAAE,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,EAAE,GAAG,KAAK,EAAE,GAAkB,EAAE,KAAc,EAAE,IAA4B,EAAE,EAAE;QACpF,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACrF,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE;QACvB,qFAAqF;QACrF,uEAAuE;QACvE,GAAG,CAAC,eAAe,IAAI,EAAE,cAAc,EAAE,IAAa,EAAE,CAAC;QACzD,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI;YAC5B,UAAU,EAAE,CAAC,GAAkB,EAAE,EAAE;gBACjC,IAAI,IAAI,GAAG,EAAE,CAAC;gBACd,OAAO,CAAC,KAAa,EAAQ,EAAE;oBAC7B,IAAI,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;oBACrC,KAAK,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,WAAW,EAAE,CAAC;wBAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;wBACtF,IAAI,CAAC,OAAO,CAAC,EAAE;4BAAE,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;oBACvE,CAAC;gBACH,CAAC,CAAC;YACJ,CAAC;SACF,CAAC;KACH,CAAqB,CAAC;AACzB,CAAC;AAED,gFAAgF;AAEhF,SAAS,MAAM,CAAC,KAAc;IAC5B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,2FAA2F;AAC3F,SAAgB,YAAY,CAC1B,QAA2B,EAC3B,OAAgE,EAAE;IAElE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC;IACrC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAiB,EAAE;QAC3C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC;QACjE,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC,CAAC;IACF,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,eAAe;QAC9B,KAAK;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,QAAQ;QACnC,KAAK,CAAC,IAAI;YACR,OAAO,IAAI,CACT,IAAI,CAAC,KAAK,KAAK,OAAO;gBACpB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;gBACnB,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAC1D,CAAC;QACJ,CAAC;QACD,uFAAuF;QACvF,kFAAkF;QAClF,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,EAAE,UAAU,EAAE,CAAC,IAAqB,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;KAC5F,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAgB,YAAY,CAAC,OAA6C,EAAE;IAC1E,MAAM,GAAG,GAAa;QACpB,8BAA8B,EAAE,QAAQ;QACxC,uBAAuB,EAAE,SAAS;QAClC,wBAAwB,EAAE,YAAY;KACvC,CAAC;IACF,OAAO;QACL,GAAG,YAAY,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;QACxF,QAAQ,EAAE,KAAK;KAChB,CAAC;AACJ,CAAC;AAOD,8FAA8F;AAC9F,SAAgB,eAAe,CAAC,MAAkB,EAAE,OAAwB,EAAE;IAC5E,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,QAAQ;QACvB,KAAK,EAAE,QAAQ;QACf,QAAQ,EAAE,QAAQ;QAClB,KAAK,CAAC,IAAI;YACR,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO;gBAC1C,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;gBACd,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;QAC/D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,SAAgB,UAAU,CAAC,SAAwB,EAAE,OAAwB,EAAE;IAC7E,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,aAAa;QAC5B,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,aAAa;QACvB,KAAK;YACH,OAAO,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACnF,CAAC;KACF,CAAC;AACJ,CAAC"}
|