@blokjs/shared 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/AgentSessionContracts.d.ts +316 -0
  2. package/dist/AgentSessionContracts.js +331 -0
  3. package/dist/BlokError.d.ts +23 -0
  4. package/dist/BlokError.js +65 -0
  5. package/dist/CapabilityContracts.d.ts +91 -0
  6. package/dist/CapabilityContracts.js +104 -0
  7. package/dist/CapabilityManifest.d.ts +67 -0
  8. package/dist/CapabilityManifest.js +172 -0
  9. package/dist/EnforcementContracts.d.ts +61 -0
  10. package/dist/EnforcementContracts.js +17 -0
  11. package/dist/EnforcementProfileContracts.d.ts +36 -0
  12. package/dist/EnforcementProfileContracts.js +55 -0
  13. package/dist/EvidenceContracts.d.ts +884 -0
  14. package/dist/EvidenceContracts.js +237 -0
  15. package/dist/GitCapabilityContracts.d.ts +103 -0
  16. package/dist/GitCapabilityContracts.js +222 -0
  17. package/dist/GlobalLogger.d.ts +2 -0
  18. package/dist/GlobalLogger.js +4 -0
  19. package/dist/GraphContracts.d.ts +1643 -0
  20. package/dist/GraphContracts.js +333 -0
  21. package/dist/InteractionContracts.d.ts +76 -0
  22. package/dist/InteractionContracts.js +218 -0
  23. package/dist/JoinContracts.d.ts +593 -0
  24. package/dist/JoinContracts.js +329 -0
  25. package/dist/NodeBase.d.ts +20 -0
  26. package/dist/NodeBase.js +57 -6
  27. package/dist/PermissionAlgebra.d.ts +51 -0
  28. package/dist/PermissionAlgebra.js +125 -0
  29. package/dist/PolicyContracts.d.ts +184 -0
  30. package/dist/PolicyContracts.js +1 -0
  31. package/dist/ProcessCapabilityContracts.d.ts +146 -0
  32. package/dist/ProcessCapabilityContracts.js +263 -0
  33. package/dist/RuntimeContracts.d.ts +125 -0
  34. package/dist/RuntimeContracts.js +108 -0
  35. package/dist/SecretContracts.d.ts +43 -0
  36. package/dist/SecretContracts.js +1 -0
  37. package/dist/WasiComponentContracts.d.ts +582 -0
  38. package/dist/WasiComponentContracts.js +192 -0
  39. package/dist/WorkflowBindingContracts.d.ts +1062 -0
  40. package/dist/WorkflowBindingContracts.js +339 -0
  41. package/dist/index.d.ts +33 -2
  42. package/dist/index.js +21 -2
  43. package/dist/types/LoggerContext.d.ts +7 -0
  44. package/dist/utils/Mapper.d.ts +14 -0
  45. package/dist/utils/Mapper.js +32 -0
  46. package/package.json +3 -2
@@ -0,0 +1,333 @@
1
+ import { z } from "zod";
2
+ /** Version of the provider-neutral graph contract. */
3
+ export const GRAPH_CONTRACT_VERSION = "1";
4
+ export const GRAPH_RESULT_STATES = [
5
+ "fresh",
6
+ "stale",
7
+ "missing",
8
+ "truncated",
9
+ "partial",
10
+ "unsupported",
11
+ "conflict",
12
+ ];
13
+ export const GRAPH_FRESHNESS_STATES = ["fresh", "stale", "unknown"];
14
+ export const GRAPH_RELATION_KINDS = [
15
+ "defines",
16
+ "contains",
17
+ "calls",
18
+ "imports",
19
+ "exports",
20
+ "references",
21
+ "extends",
22
+ "implements",
23
+ "overrides",
24
+ "tests",
25
+ ];
26
+ export const GRAPH_SYMBOL_KINDS = [
27
+ "file",
28
+ "module",
29
+ "namespace",
30
+ "class",
31
+ "interface",
32
+ "enum",
33
+ "function",
34
+ "method",
35
+ "property",
36
+ "variable",
37
+ "type",
38
+ "unknown",
39
+ ];
40
+ export const GRAPH_SEARCH_KINDS = ["symbol", "file", "module", "text"];
41
+ export const GRAPH_INDEX_SOURCES = ["authoritative", "uncommitted-overlay", "provider"];
42
+ export const GRAPH_RELATION_DIRECTIONS = ["inbound", "outbound", "both"];
43
+ export const GRAPH_MAX_STRING_LENGTH = 512;
44
+ export const GRAPH_MAX_PATH_LENGTH = 1024;
45
+ export const GRAPH_MAX_ITEMS = 500;
46
+ export const GRAPH_MAX_FILES_PER_INDEX = 500;
47
+ export const GRAPH_MAX_INDEX_BYTES = 16 * 1024 * 1024;
48
+ export const GRAPH_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
49
+ export const GRAPH_MAX_DEPTH = 32;
50
+ const IDENTIFIER = /^[A-Za-z][A-Za-z0-9._:/-]{0,127}$/;
51
+ const PATH = /^[^\0]{1,1024}$/;
52
+ const DIGEST = /^(sha256):[0-9a-f]{64}$|^(sha512):[0-9a-f]{128}$/i;
53
+ const identifier = z.string().min(1).max(128).regex(IDENTIFIER, "must be a bounded identifier");
54
+ const boundedString = z.string().min(1).max(GRAPH_MAX_STRING_LENGTH);
55
+ const path = z.string().min(1).max(GRAPH_MAX_PATH_LENGTH).regex(PATH, "must be a bounded path");
56
+ const digest = z
57
+ .string()
58
+ .max(140)
59
+ .regex(DIGEST, "must be a sha256: or sha512: digest with the complete hexadecimal length")
60
+ .transform((value) => value.toLowerCase());
61
+ const timestamp = z.string().min(1).max(64);
62
+ export class GraphContractError extends Error {
63
+ errors;
64
+ constructor(errors) {
65
+ super(`Invalid graph contract: ${errors.join("; ")}`);
66
+ this.name = "GraphContractError";
67
+ this.errors = [...errors];
68
+ }
69
+ }
70
+ const repositoryIdentitySchema = z.object({
71
+ provider: identifier,
72
+ id: identifier,
73
+ revision: boundedString.optional(),
74
+ });
75
+ const worktreeSchema = z.object({
76
+ id: identifier,
77
+ branch: boundedString.optional(),
78
+ commit: boundedString.optional(),
79
+ dirty: z.boolean().optional(),
80
+ overlay: z.enum(["clean", "uncommitted", "unknown"]).optional(),
81
+ });
82
+ const contentHashesSchema = z.record(path, digest);
83
+ const scopeSchema = z.object({
84
+ repository: repositoryIdentitySchema,
85
+ worktree: worktreeSchema.optional(),
86
+ commit: boundedString.optional(),
87
+ contentHashes: contentHashesSchema.optional(),
88
+ });
89
+ const positionSchema = z.object({
90
+ line: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
91
+ column: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
92
+ });
93
+ const rangeSchema = z.object({ start: positionSchema, end: positionSchema.optional() });
94
+ const locationSchema = z.object({
95
+ path,
96
+ range: rangeSchema.optional(),
97
+ language: identifier.optional(),
98
+ contentHash: digest.optional(),
99
+ });
100
+ const provenanceSchema = z.object({
101
+ source: z.literal("derived-index"),
102
+ provider: identifier,
103
+ providerVersion: boundedString.optional(),
104
+ indexVersion: boundedString,
105
+ repository: repositoryIdentitySchema,
106
+ worktree: worktreeSchema.optional(),
107
+ commit: boundedString.optional(),
108
+ contentHash: digest.optional(),
109
+ path: path.optional(),
110
+ range: rangeSchema.optional(),
111
+ indexedAt: timestamp.optional(),
112
+ });
113
+ const freshnessSchema = z.object({
114
+ state: z.enum(GRAPH_FRESHNESS_STATES),
115
+ indexedAt: timestamp.optional(),
116
+ checkedAt: timestamp,
117
+ indexedCommit: boundedString.optional(),
118
+ observedCommit: boundedString.optional(),
119
+ indexedContentHash: digest.optional(),
120
+ observedContentHash: digest.optional(),
121
+ reason: boundedString.optional(),
122
+ });
123
+ const resultStatusSchema = z
124
+ .object({
125
+ primary: z.enum(GRAPH_RESULT_STATES),
126
+ states: z.array(z.enum(GRAPH_RESULT_STATES)).min(1).max(GRAPH_RESULT_STATES.length),
127
+ complete: z.boolean(),
128
+ })
129
+ .superRefine((value, context) => {
130
+ if (!value.states.includes(value.primary)) {
131
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["primary"], message: "primary must be listed in states" });
132
+ }
133
+ if (new Set(value.states).size !== value.states.length) {
134
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["states"], message: "states must be unique" });
135
+ }
136
+ const completeStates = value.states.every((state) => state === "fresh");
137
+ if (value.complete !== completeStates) {
138
+ context.addIssue({
139
+ code: z.ZodIssueCode.custom,
140
+ path: ["complete"],
141
+ message: "complete is true only for a fresh result",
142
+ });
143
+ }
144
+ });
145
+ const errorSchema = z.object({
146
+ code: identifier,
147
+ category: z.enum([
148
+ "provider-unavailable",
149
+ "invalid-query",
150
+ "not-found",
151
+ "unsupported",
152
+ "stale",
153
+ "conflict",
154
+ "limit-exceeded",
155
+ "cancelled",
156
+ "index-failed",
157
+ "internal",
158
+ ]),
159
+ message: boundedString,
160
+ retryable: z.boolean(),
161
+ guidance: z.enum(["reread-authoritative-source", "retry", "narrow-query", "inspect-provider", "none"]),
162
+ path: path.optional(),
163
+ });
164
+ const symbolSchema = z.object({
165
+ id: identifier,
166
+ name: boundedString,
167
+ kind: z.enum(GRAPH_SYMBOL_KINDS),
168
+ location: locationSchema,
169
+ language: identifier.optional(),
170
+ signature: boundedString.optional(),
171
+ containerId: identifier.optional(),
172
+ exported: z.boolean().optional(),
173
+ });
174
+ const relationSchema = z.object({
175
+ id: identifier,
176
+ from: identifier,
177
+ to: identifier,
178
+ kind: z.enum(GRAPH_RELATION_KINDS),
179
+ location: locationSchema.optional(),
180
+ });
181
+ const searchHitSchema = z.object({
182
+ id: identifier,
183
+ kind: z.enum(GRAPH_SEARCH_KINDS),
184
+ name: boundedString,
185
+ score: z.number().finite().min(0).max(1),
186
+ symbol: symbolSchema.optional(),
187
+ location: locationSchema.optional(),
188
+ });
189
+ const requestLimit = z.number().int().positive().max(GRAPH_MAX_ITEMS).optional();
190
+ export const GraphRepositoryIdentitySchema = repositoryIdentitySchema;
191
+ export const GraphWorktreeIdentitySchema = worktreeSchema;
192
+ export const GraphScopeSchema = scopeSchema;
193
+ export const GraphRangeSchema = rangeSchema;
194
+ export const GraphLocationSchema = locationSchema;
195
+ export const GraphProvenanceSchema = provenanceSchema;
196
+ export const GraphFreshnessSchema = freshnessSchema;
197
+ export const GraphResultStatusSchema = resultStatusSchema;
198
+ export const GraphErrorSchema = errorSchema;
199
+ export const GraphSymbolSchema = symbolSchema;
200
+ export const GraphRelationSchema = relationSchema;
201
+ export const GraphSearchHitSchema = searchHitSchema;
202
+ export const GraphSearchRequestSchema = z.object({
203
+ scope: scopeSchema,
204
+ query: boundedString,
205
+ kinds: z.array(z.enum(GRAPH_SEARCH_KINDS)).max(GRAPH_SEARCH_KINDS.length).optional(),
206
+ pathPrefix: path.optional(),
207
+ limit: requestLimit,
208
+ cursor: boundedString.optional(),
209
+ });
210
+ export const GraphSymbolRequestSchema = z
211
+ .object({
212
+ scope: scopeSchema,
213
+ symbolId: identifier.optional(),
214
+ name: boundedString.optional(),
215
+ path: path.optional(),
216
+ limit: requestLimit,
217
+ })
218
+ .refine((value) => value.symbolId !== undefined || value.name !== undefined, "symbolId or name is required");
219
+ export const GraphRelationRequestSchema = z.object({
220
+ scope: scopeSchema,
221
+ symbolId: identifier,
222
+ direction: z.enum(GRAPH_RELATION_DIRECTIONS).optional(),
223
+ kinds: z.array(z.enum(GRAPH_RELATION_KINDS)).max(GRAPH_RELATION_KINDS.length).optional(),
224
+ depth: z.number().int().positive().max(GRAPH_MAX_DEPTH).optional(),
225
+ limit: requestLimit,
226
+ });
227
+ export const GraphImpactRequestSchema = z.object({
228
+ scope: scopeSchema,
229
+ symbolId: identifier,
230
+ direction: z.enum(["inbound", "outbound"]).optional(),
231
+ relationKinds: z.array(z.enum(GRAPH_RELATION_KINDS)).max(GRAPH_RELATION_KINDS.length).optional(),
232
+ maxDepth: z.number().int().positive().max(GRAPH_MAX_DEPTH).optional(),
233
+ limit: requestLimit,
234
+ });
235
+ export const GraphFreshnessRequestSchema = z.object({
236
+ scope: scopeSchema,
237
+ paths: z.array(path).max(GRAPH_MAX_ITEMS).optional(),
238
+ });
239
+ export const GraphIndexFileSchema = z.object({
240
+ path,
241
+ contentHash: digest,
242
+ source: z.enum(GRAPH_INDEX_SOURCES).optional(),
243
+ language: identifier.optional(),
244
+ symbols: z.array(symbolSchema).max(GRAPH_MAX_ITEMS),
245
+ relations: z.array(relationSchema).max(GRAPH_MAX_ITEMS),
246
+ });
247
+ export const GraphIndexRequestSchema = z.object({
248
+ scope: scopeSchema,
249
+ files: z.array(GraphIndexFileSchema).min(1).max(GRAPH_MAX_FILES_PER_INDEX),
250
+ indexVersion: boundedString.optional(),
251
+ reason: z.enum(["initial", "changed-files", "branch-switch", "committed-patch", "manual"]).optional(),
252
+ });
253
+ export const AuthoritativeSourceReadRequestSchema = z.object({
254
+ scope: scopeSchema,
255
+ path,
256
+ expected: z
257
+ .object({ commit: boundedString.optional(), contentHash: digest.optional() })
258
+ .refine((value) => value.commit !== undefined || value.contentHash !== undefined, "an expected version is required")
259
+ .optional(),
260
+ });
261
+ export const AuthoritativeSourceSnapshotSchema = z.object({
262
+ scope: scopeSchema,
263
+ path,
264
+ contentHash: digest,
265
+ commit: boundedString.optional(),
266
+ bytes: z.number().int().nonnegative().max(GRAPH_MAX_INDEX_BYTES),
267
+ content: z.string().max(GRAPH_MAX_INDEX_BYTES),
268
+ });
269
+ const baseResponseSchema = z.object({
270
+ version: z.literal(GRAPH_CONTRACT_VERSION),
271
+ authority: z.literal("navigation-only"),
272
+ status: resultStatusSchema,
273
+ freshness: freshnessSchema,
274
+ provenance: provenanceSchema.optional(),
275
+ errors: z.array(errorSchema).max(GRAPH_MAX_ITEMS),
276
+ nextCursor: boundedString.optional(),
277
+ });
278
+ function assertRecordSize(value, label, maximum) {
279
+ let serialized;
280
+ try {
281
+ serialized = JSON.stringify(value);
282
+ }
283
+ catch {
284
+ throw new GraphContractError([`${label} must be JSON-serializable`]);
285
+ }
286
+ if (new TextEncoder().encode(serialized).byteLength > maximum) {
287
+ throw new GraphContractError([`${label} exceeds ${maximum} bytes`]);
288
+ }
289
+ return value;
290
+ }
291
+ function parse(schema, value, label) {
292
+ const result = schema.safeParse(value);
293
+ if (!result.success) {
294
+ throw new GraphContractError(result.error.issues.map((issue) => `${label}${issue.path.length > 0 ? `.${issue.path.join(".")}` : ""} ${issue.message}`));
295
+ }
296
+ return result.data;
297
+ }
298
+ export function parseGraphScope(value) {
299
+ return parse(scopeSchema, value, "graph scope");
300
+ }
301
+ export function parseGraphSearchRequest(value) {
302
+ return parse(GraphSearchRequestSchema, value, "graph search request");
303
+ }
304
+ export function parseGraphSymbolRequest(value) {
305
+ return parse(GraphSymbolRequestSchema, value, "graph symbol request");
306
+ }
307
+ export function parseGraphRelationRequest(value) {
308
+ return parse(GraphRelationRequestSchema, value, "graph relation request");
309
+ }
310
+ export function parseGraphImpactRequest(value) {
311
+ return parse(GraphImpactRequestSchema, value, "graph impact request");
312
+ }
313
+ export function parseGraphFreshnessRequest(value) {
314
+ return parse(GraphFreshnessRequestSchema, value, "graph freshness request");
315
+ }
316
+ export function parseGraphIndexRequest(value) {
317
+ return assertRecordSize(parse(GraphIndexRequestSchema, value, "graph index request"), "graph index request", GRAPH_MAX_INDEX_BYTES);
318
+ }
319
+ export function parseAuthoritativeSourceReadRequest(value) {
320
+ return parse(AuthoritativeSourceReadRequestSchema, value, "authoritative source read request");
321
+ }
322
+ export function parseGraphQueryResponse(itemSchema, value) {
323
+ return assertRecordSize(parse(baseResponseSchema.extend({ items: z.array(itemSchema).max(GRAPH_MAX_ITEMS) }), value, "graph query response"), "graph query response", GRAPH_MAX_RESPONSE_BYTES);
324
+ }
325
+ export function parseGraphIndexResponse(value) {
326
+ return assertRecordSize(parse(baseResponseSchema.omit({ nextCursor: true }).extend({
327
+ indexedFiles: z.array(path).max(GRAPH_MAX_FILES_PER_INDEX),
328
+ skippedFiles: z.array(path).max(GRAPH_MAX_FILES_PER_INDEX),
329
+ }), value, "graph index response"), "graph index response", GRAPH_MAX_RESPONSE_BYTES);
330
+ }
331
+ export function serializeGraphContract(value) {
332
+ return JSON.stringify(value);
333
+ }
@@ -0,0 +1,76 @@
1
+ import type { InteractionSuspension, PolicyDecision, PolicyRequest } from "./PolicyContracts.js";
2
+ export declare const INTERACTION_VERSION: "1";
3
+ /** The maximum UTF-8 size of an answer or any nested answer value. */
4
+ export declare const INTERACTION_MAX_PAYLOAD_BYTES: number;
5
+ /** Limits protect persistence and control-plane consumers from pathological JSON. */
6
+ export declare const INTERACTION_MAX_PAYLOAD_DEPTH = 8;
7
+ export declare const INTERACTION_MAX_PAYLOAD_ITEMS = 256;
8
+ export declare const INTERACTION_MAX_STRING_LENGTH: number;
9
+ export declare const INTERACTION_MAX_LINEAGE_DEPTH = 32;
10
+ export declare const INTERACTION_MAX_LINEAGE_PATH = 32;
11
+ export declare const INTERACTION_REDACTED_VALUE: "[REDACTED]";
12
+ export type InteractionStatus = "pending" | "answered" | "denied" | "expired" | "cancelled";
13
+ /** JSON-only values accepted at the durable interaction boundary. */
14
+ export type InteractionPayload = string | number | boolean | null | readonly InteractionPayload[] | Readonly<{
15
+ [key: string]: InteractionPayload;
16
+ }>;
17
+ export declare class InteractionContractError extends Error {
18
+ readonly code = "INTERACTION_INVALID";
19
+ constructor(message: string);
20
+ }
21
+ export interface InteractionRecord {
22
+ readonly version: typeof INTERACTION_VERSION;
23
+ readonly id: string;
24
+ readonly request: PolicyRequest;
25
+ readonly decision: PolicyDecision;
26
+ readonly status: InteractionStatus;
27
+ readonly createdAt: string;
28
+ readonly expiresAt: string;
29
+ readonly sequence: number;
30
+ readonly answer?: InteractionPayload;
31
+ readonly answeredBy?: string;
32
+ readonly answeredAt?: string;
33
+ /** Set when the answered record is atomically claimed for resumption. */
34
+ readonly claimedBy?: string;
35
+ readonly claimedAt?: string;
36
+ /** Reference to the suspended run/cursor; persisted separately from trace state. */
37
+ readonly suspension?: InteractionSuspension;
38
+ }
39
+ export interface InteractionAnswer {
40
+ readonly id: string;
41
+ readonly principalId: string;
42
+ readonly answer?: InteractionPayload;
43
+ readonly deny?: boolean;
44
+ readonly sequence: number;
45
+ }
46
+ export interface InteractionStore {
47
+ create(request: PolicyRequest, decision: PolicyDecision, opts?: {
48
+ expiresAt?: string;
49
+ }): Promise<InteractionRecord>;
50
+ get(id: string): Promise<InteractionRecord | undefined>;
51
+ answer(answer: InteractionAnswer): Promise<InteractionRecord>;
52
+ /**
53
+ * Atomically consume an answered interaction for one resume attempt.
54
+ * Implementations must compare both the principal and expected sequence
55
+ * in the same transaction as the claim.
56
+ */
57
+ claim(id: string, principalId: string, sequence: number): Promise<InteractionRecord>;
58
+ cancel(id: string, principalId: string, sequence: number): Promise<InteractionRecord>;
59
+ expire(now?: string): Promise<readonly InteractionRecord[]>;
60
+ }
61
+ /** Validate and clone a JSON-only interaction payload. */
62
+ export declare function parseInteractionPayload(value: unknown, path?: string): InteractionPayload;
63
+ /** Validate an answer received from an untrusted control-plane caller. */
64
+ export declare function parseInteractionAnswer(value: unknown): InteractionAnswer;
65
+ /** Make a bounded, JSON-safe snapshot for records and observability sinks. */
66
+ export declare function redactInteractionPayload(value: InteractionPayload): InteractionPayload;
67
+ /** Redact and bound provider-controlled text such as rule IDs and reasons. */
68
+ export declare function redactInteractionString(value: string): string;
69
+ /** Remove non-persistable runtime state and redact policy metadata for a record. */
70
+ export declare function redactInteractionRequest(request: PolicyRequest): PolicyRequest;
71
+ /** Bound and redact provider-controlled decision text before persistence. */
72
+ export declare function redactInteractionDecision(decision: PolicyDecision): PolicyDecision;
73
+ /** Return an isolated, deeply immutable snapshot suitable for a store/API. */
74
+ export declare function immutableInteractionSnapshot<T>(value: T): T;
75
+ /** Canonical comparison for duplicate answers with different object key order. */
76
+ export declare function fingerprintInteractionPayload(value: InteractionPayload | undefined): string;
@@ -0,0 +1,218 @@
1
+ export const INTERACTION_VERSION = "1";
2
+ /** The maximum UTF-8 size of an answer or any nested answer value. */
3
+ export const INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
4
+ /** Limits protect persistence and control-plane consumers from pathological JSON. */
5
+ export const INTERACTION_MAX_PAYLOAD_DEPTH = 8;
6
+ export const INTERACTION_MAX_PAYLOAD_ITEMS = 256;
7
+ export const INTERACTION_MAX_STRING_LENGTH = 8 * 1024;
8
+ export const INTERACTION_MAX_LINEAGE_DEPTH = 32;
9
+ export const INTERACTION_MAX_LINEAGE_PATH = 32;
10
+ export const INTERACTION_REDACTED_VALUE = "[REDACTED]";
11
+ export class InteractionContractError extends Error {
12
+ code = "INTERACTION_INVALID";
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "InteractionContractError";
16
+ }
17
+ }
18
+ const SENSITIVE_KEY = /(?:pass(?:word|code)?|secret|token|api[-_]?key|authorization|cookie|credential|private[-_]?key|session)/i;
19
+ const SENSITIVE_TEXT = /(?:bearer\s+|\b(?:pass(?:word|code)?|secret|token|api[-_]?key|authorization|cookie|credential|private[-_]?key)\b\s*[:=]?)/i;
20
+ function isRecord(value) {
21
+ return value !== null && typeof value === "object" && !Array.isArray(value);
22
+ }
23
+ function byteLength(value) {
24
+ return new TextEncoder().encode(value).byteLength;
25
+ }
26
+ function fail(path, message) {
27
+ throw new InteractionContractError(`${path} ${message}`);
28
+ }
29
+ function validatePayload(value, path, seen, depth) {
30
+ if (depth > INTERACTION_MAX_PAYLOAD_DEPTH)
31
+ fail(path, `exceeds maximum depth of ${INTERACTION_MAX_PAYLOAD_DEPTH}`);
32
+ if (value === null)
33
+ return value;
34
+ if (typeof value === "string") {
35
+ if (value.length > INTERACTION_MAX_STRING_LENGTH)
36
+ fail(path, "contains an oversized string");
37
+ return value;
38
+ }
39
+ if (typeof value === "boolean")
40
+ return value;
41
+ if (typeof value === "number") {
42
+ if (!Number.isFinite(value))
43
+ fail(path, "must contain only finite numbers");
44
+ return value;
45
+ }
46
+ if (typeof value !== "object")
47
+ fail(path, "must be JSON-serializable");
48
+ if (seen.has(value))
49
+ fail(path, "must not contain circular references");
50
+ seen.add(value);
51
+ try {
52
+ if (Array.isArray(value)) {
53
+ if (value.length > INTERACTION_MAX_PAYLOAD_ITEMS)
54
+ fail(path, "contains too many items");
55
+ return value.map((item, index) => validatePayload(item, `${path}[${index}]`, seen, depth + 1));
56
+ }
57
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
58
+ fail(path, "must contain only plain objects");
59
+ const keys = Object.keys(value);
60
+ if (keys.length > INTERACTION_MAX_PAYLOAD_ITEMS)
61
+ fail(path, "contains too many fields");
62
+ const output = {};
63
+ const objectValue = value;
64
+ for (const key of keys)
65
+ output[key] = validatePayload(objectValue[key], `${path}.${key}`, seen, depth + 1);
66
+ return output;
67
+ }
68
+ finally {
69
+ seen.delete(value);
70
+ }
71
+ }
72
+ /** Validate and clone a JSON-only interaction payload. */
73
+ export function parseInteractionPayload(value, path = "interaction payload") {
74
+ const parsed = validatePayload(value, path, new WeakSet(), 0);
75
+ let serialized;
76
+ try {
77
+ serialized = JSON.stringify(parsed);
78
+ }
79
+ catch {
80
+ throw new InteractionContractError(`${path} must be JSON-serializable`);
81
+ }
82
+ if (byteLength(serialized) > INTERACTION_MAX_PAYLOAD_BYTES)
83
+ throw new InteractionContractError(`${path} exceeds ${INTERACTION_MAX_PAYLOAD_BYTES} bytes`);
84
+ return parsed;
85
+ }
86
+ function boundedIdentifier(value, path) {
87
+ if (typeof value !== "string" || value.length === 0 || value.length > 256)
88
+ fail(path, "must be a non-empty string of at most 256 characters");
89
+ return value;
90
+ }
91
+ function parseAttribution(value) {
92
+ if (value === undefined)
93
+ return undefined;
94
+ if (!isRecord(value))
95
+ fail("interaction attribution", "must be an object");
96
+ const rootId = boundedIdentifier(value.rootId, "interaction attribution.rootId");
97
+ const depth = value.depth;
98
+ if (typeof depth !== "number" || !Number.isSafeInteger(depth) || depth < 0 || depth > INTERACTION_MAX_LINEAGE_DEPTH)
99
+ fail("interaction attribution.depth", `must be an integer from 0 to ${INTERACTION_MAX_LINEAGE_DEPTH}`);
100
+ const result = { rootId, depth };
101
+ for (const key of ["parentId", "branchId"]) {
102
+ if (value[key] !== undefined)
103
+ result[key] = boundedIdentifier(value[key], `interaction attribution.${key}`);
104
+ }
105
+ const branchIndex = value.branchIndex;
106
+ if (branchIndex !== undefined) {
107
+ if (typeof branchIndex !== "number" || !Number.isSafeInteger(branchIndex) || branchIndex < 0)
108
+ fail("interaction attribution.branchIndex", "must be a non-negative safe integer");
109
+ result.branchIndex = branchIndex;
110
+ }
111
+ if (value.branchPath !== undefined) {
112
+ if (!Array.isArray(value.branchPath) || value.branchPath.length > INTERACTION_MAX_LINEAGE_PATH)
113
+ fail("interaction attribution.branchPath", `must contain at most ${INTERACTION_MAX_LINEAGE_PATH} labels`);
114
+ result.branchPath = value.branchPath.map((item, index) => boundedIdentifier(item, `interaction attribution.branchPath[${index}]`));
115
+ }
116
+ return result;
117
+ }
118
+ /** Validate an answer received from an untrusted control-plane caller. */
119
+ export function parseInteractionAnswer(value) {
120
+ if (!isRecord(value))
121
+ throw new InteractionContractError("interaction answer must be an object");
122
+ const id = boundedIdentifier(value.id, "interaction answer.id");
123
+ const principalId = boundedIdentifier(value.principalId, "interaction answer.principalId");
124
+ const sequence = value.sequence;
125
+ if (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence < 0)
126
+ fail("interaction answer.sequence", "must be a non-negative safe integer");
127
+ if (value.deny !== undefined && typeof value.deny !== "boolean")
128
+ fail("interaction answer.deny", "must be a boolean");
129
+ const answer = value.answer === undefined ? undefined : parseInteractionPayload(value.answer, "interaction answer.answer");
130
+ return {
131
+ id,
132
+ principalId,
133
+ sequence,
134
+ ...(answer === undefined ? {} : { answer }),
135
+ ...(value.deny === undefined ? {} : { deny: value.deny }),
136
+ };
137
+ }
138
+ function redactText(value) {
139
+ return SENSITIVE_TEXT.test(value) ? INTERACTION_REDACTED_VALUE : value.slice(0, INTERACTION_MAX_STRING_LENGTH);
140
+ }
141
+ /** Make a bounded, JSON-safe snapshot for records and observability sinks. */
142
+ export function redactInteractionPayload(value) {
143
+ if (typeof value === "string")
144
+ return redactText(value);
145
+ if (value === null || typeof value === "number" || typeof value === "boolean")
146
+ return value;
147
+ if (Array.isArray(value))
148
+ return value.map((item) => redactInteractionPayload(item));
149
+ const output = {};
150
+ for (const [key, item] of Object.entries(value)) {
151
+ output[key] = SENSITIVE_KEY.test(key) ? INTERACTION_REDACTED_VALUE : redactInteractionPayload(item);
152
+ }
153
+ return output;
154
+ }
155
+ /** Redact and bound provider-controlled text such as rule IDs and reasons. */
156
+ export function redactInteractionString(value) {
157
+ return redactText(value);
158
+ }
159
+ function redactFragments(fragments) {
160
+ const result = {};
161
+ for (const [key, value] of Object.entries(fragments)) {
162
+ result[key] =
163
+ SENSITIVE_KEY.test(key) || (typeof value === "string" && SENSITIVE_TEXT.test(value))
164
+ ? INTERACTION_REDACTED_VALUE
165
+ : value;
166
+ }
167
+ return result;
168
+ }
169
+ /** Remove non-persistable runtime state and redact policy metadata for a record. */
170
+ export function redactInteractionRequest(request) {
171
+ const attribution = parseAttribution(request.attribution);
172
+ const { signal: _signal, ...persistable } = request;
173
+ return {
174
+ ...persistable,
175
+ ...(attribution ? { attribution } : {}),
176
+ scope: { ...request.scope, fragments: redactFragments(request.scope.fragments) },
177
+ layers: request.layers.map((layer) => ({ ...layer })),
178
+ };
179
+ }
180
+ /** Bound and redact provider-controlled decision text before persistence. */
181
+ export function redactInteractionDecision(decision) {
182
+ return {
183
+ ...decision,
184
+ id: boundedIdentifier(decision.id, "interaction decision.id"),
185
+ policyVersion: boundedIdentifier(decision.policyVersion, "interaction decision.policyVersion"),
186
+ reasonCode: redactText(boundedIdentifier(decision.reasonCode, "interaction decision.reasonCode")),
187
+ ...(decision.reason === undefined
188
+ ? {}
189
+ : { reason: redactText(decision.reason.slice(0, INTERACTION_MAX_STRING_LENGTH)) }),
190
+ };
191
+ }
192
+ /** Return an isolated, deeply immutable snapshot suitable for a store/API. */
193
+ export function immutableInteractionSnapshot(value) {
194
+ const snapshot = structuredClone(value);
195
+ const freeze = (item) => {
196
+ if (item === null || typeof item !== "object" || Object.isFrozen(item))
197
+ return;
198
+ for (const child of Object.values(item))
199
+ freeze(child);
200
+ Object.freeze(item);
201
+ };
202
+ freeze(snapshot);
203
+ return snapshot;
204
+ }
205
+ /** Canonical comparison for duplicate answers with different object key order. */
206
+ export function fingerprintInteractionPayload(value) {
207
+ if (value === undefined)
208
+ return "undefined";
209
+ if (value === null || typeof value !== "object")
210
+ return JSON.stringify(value);
211
+ if (Array.isArray(value))
212
+ return `[${value.map(fingerprintInteractionPayload).join(",")}]`;
213
+ const objectValue = value;
214
+ return `{${Object.keys(objectValue)
215
+ .sort()
216
+ .map((key) => `${JSON.stringify(key)}:${fingerprintInteractionPayload(objectValue[key])}`)
217
+ .join(",")}}`;
218
+ }