@humain/humain-code-contracts 0.3.2 → 0.4.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +10 -1
  3. package/dist/flow-admission.d.ts +209 -0
  4. package/dist/flow-admission.d.ts.map +1 -0
  5. package/dist/flow-admission.js +59 -0
  6. package/dist/flow-admission.js.map +1 -0
  7. package/dist/flow-common.d.ts +103 -0
  8. package/dist/flow-common.d.ts.map +1 -0
  9. package/dist/flow-common.js +282 -0
  10. package/dist/flow-common.js.map +1 -0
  11. package/dist/flow-control.d.ts +107 -0
  12. package/dist/flow-control.d.ts.map +1 -0
  13. package/dist/flow-control.js +144 -0
  14. package/dist/flow-control.js.map +1 -0
  15. package/dist/flow-events.d.ts +345 -0
  16. package/dist/flow-events.d.ts.map +1 -0
  17. package/dist/flow-events.js +147 -0
  18. package/dist/flow-events.js.map +1 -0
  19. package/dist/flow-profile.d.ts +200 -0
  20. package/dist/flow-profile.d.ts.map +1 -0
  21. package/dist/flow-profile.js +89 -0
  22. package/dist/flow-profile.js.map +1 -0
  23. package/dist/flow-sizing.d.ts +30 -0
  24. package/dist/flow-sizing.d.ts.map +1 -0
  25. package/dist/flow-sizing.js +568 -0
  26. package/dist/flow-sizing.js.map +1 -0
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/rpc.d.ts +27 -2
  32. package/dist/rpc.d.ts.map +1 -1
  33. package/dist/rpc.js +283 -18
  34. package/dist/rpc.js.map +1 -1
  35. package/dist/trace-artifact.d.ts +1 -1
  36. package/dist/trace-artifact.d.ts.map +1 -1
  37. package/dist/trace-artifact.js +3 -1
  38. package/dist/trace-artifact.js.map +1 -1
  39. package/dist/trace-http.d.ts.map +1 -1
  40. package/dist/trace-http.js +3 -0
  41. package/dist/trace-http.js.map +1 -1
  42. package/dist/trace-payload.d.ts +25 -1
  43. package/dist/trace-payload.d.ts.map +1 -1
  44. package/dist/trace-payload.js +2 -0
  45. package/dist/trace-payload.js.map +1 -1
  46. package/dist/trace-records.d.ts +985 -0
  47. package/dist/trace-records.d.ts.map +1 -0
  48. package/dist/trace-records.js +535 -0
  49. package/dist/trace-records.js.map +1 -0
  50. package/dist/trace.d.ts +1 -0
  51. package/dist/trace.d.ts.map +1 -1
  52. package/dist/trace.js +1 -0
  53. package/dist/trace.js.map +1 -1
  54. package/package.json +29 -5
@@ -0,0 +1,282 @@
1
+ import { Type } from "typebox";
2
+ import { Compile } from "typebox/compile";
3
+ /**
4
+ * Shared primitives for the `./flow-*` contract subpaths. TypeBox-only and browser-safe: no node:* imports,
5
+ * no hashing (callers inject `sha256Hex`), no free-text string types.
6
+ */
7
+ export const FLOW_ID_PATTERN = "^[a-z0-9][a-z0-9_.:-]{0,127}$";
8
+ /** Model ids may be provider-qualified: lowercase, at most one `/`, no empty segment, <= 128 chars in total. */
9
+ export const FLOW_MODEL_ID_PATTERN = "^(?=.{1,128}$)[a-z0-9][a-z0-9_.:-]*(?:/[a-z0-9][a-z0-9_.:-]*)?$";
10
+ export const FLOW_DIGEST_PATTERN = "^[a-f0-9]{64}$";
11
+ const FLOW_TS_PATTERN = "^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d{1,9})?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$";
12
+ export const FLOW_CONTRACT_ERROR_CODES = {
13
+ profile_invalid: "Invalid flow profile",
14
+ admission_invalid: "Invalid flow run admission",
15
+ event_invalid: "Invalid flow event",
16
+ command_invalid: "Invalid flow control command",
17
+ receipt_invalid: "Invalid flow control receipt",
18
+ status_invalid: "Invalid flow status",
19
+ digest_mismatch: "Flow digest mismatch",
20
+ digest_unverifiable: "Flow digest cannot be verified",
21
+ canonical_invalid: "Flow data cannot be canonicalized",
22
+ receipt_conflict: "Flow receipt conflicts with the first receipt",
23
+ };
24
+ /** Messages are constants chosen by contract code from `code`; rejected input is never echoed. */
25
+ export class FlowContractError extends Error {
26
+ code;
27
+ constructor(code) {
28
+ super(FLOW_CONTRACT_ERROR_CODES[code]);
29
+ this.name = "FlowContractError";
30
+ this.code = code;
31
+ }
32
+ }
33
+ /** Union of string literals with a precise Static type (a mapped array would infer `never`). */
34
+ export const lit = (...values) => Type.Union(values.map((value) => Type.Literal(value)));
35
+ export const StrictObject = (properties) => Type.Object(properties, { additionalProperties: false });
36
+ export const FlowIdSchema = Type.String({ pattern: FLOW_ID_PATTERN });
37
+ export const FlowDigestSchema = Type.String({ pattern: FLOW_DIGEST_PATTERN });
38
+ const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
39
+ /** Pattern-valid timestamps only: rejects impossible calendar dates such as 2026-02-31 or a non-leap Feb 29. */
40
+ function isRealCalendarDate(timestamp) {
41
+ const year = Number(timestamp.slice(0, 4));
42
+ const month = Number(timestamp.slice(5, 7));
43
+ const day = Number(timestamp.slice(8, 10));
44
+ const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
45
+ return day <= (month === 2 && leap ? 29 : (DAYS_IN_MONTH[month - 1] ?? 0));
46
+ }
47
+ export const FlowTimestampSchema = Type.Refine(Type.String({ pattern: FLOW_TS_PATTERN }), isRealCalendarDate);
48
+ export const FlowMicrousdSchema = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
49
+ export const FlowCountSchema = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
50
+ export const FlowPositiveIntSchema = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
51
+ export const FlowModelRefSchema = StrictObject({
52
+ provider: FlowIdSchema,
53
+ model: Type.String({ pattern: FLOW_MODEL_ID_PATTERN }),
54
+ });
55
+ export const FLOW_ROLES = [
56
+ "scout",
57
+ "worker",
58
+ "implementer",
59
+ "lead",
60
+ "lead_large",
61
+ "architect",
62
+ "reviewer",
63
+ "security_reviewer",
64
+ "qa",
65
+ "triage",
66
+ ];
67
+ export const FlowRoleSchema = Type.Union([
68
+ Type.Literal("scout"),
69
+ Type.Literal("worker"),
70
+ Type.Literal("implementer"),
71
+ Type.Literal("lead"),
72
+ Type.Literal("lead_large"),
73
+ Type.Literal("architect"),
74
+ Type.Literal("reviewer"),
75
+ Type.Literal("security_reviewer"),
76
+ Type.Literal("qa"),
77
+ Type.Literal("triage"),
78
+ ]);
79
+ /** `Partial<Record<FlowRole, T>>` as a strict object. */
80
+ export const partialByRole = (schema) => StrictObject({
81
+ scout: Type.Optional(schema),
82
+ worker: Type.Optional(schema),
83
+ implementer: Type.Optional(schema),
84
+ lead: Type.Optional(schema),
85
+ lead_large: Type.Optional(schema),
86
+ architect: Type.Optional(schema),
87
+ reviewer: Type.Optional(schema),
88
+ security_reviewer: Type.Optional(schema),
89
+ qa: Type.Optional(schema),
90
+ triage: Type.Optional(schema),
91
+ });
92
+ export const FLOW_SIZES = ["small", "medium", "large"];
93
+ export const FlowSizeSchema = Type.Union([Type.Literal("small"), Type.Literal("medium"), Type.Literal("large")]);
94
+ export const FLOW_FAILURE_CLASSES = [
95
+ "provider_error",
96
+ "rate_limit",
97
+ "timeout",
98
+ "check_failed",
99
+ "review_rejected",
100
+ "qa_failed",
101
+ "budget_stop",
102
+ "cancelled",
103
+ "crash",
104
+ "unsafe_scope",
105
+ "stalled",
106
+ "config_invalid",
107
+ "internal",
108
+ ];
109
+ export const FlowFailureClassSchema = Type.Union([
110
+ Type.Literal("provider_error"),
111
+ Type.Literal("rate_limit"),
112
+ Type.Literal("timeout"),
113
+ Type.Literal("check_failed"),
114
+ Type.Literal("review_rejected"),
115
+ Type.Literal("qa_failed"),
116
+ Type.Literal("budget_stop"),
117
+ Type.Literal("cancelled"),
118
+ Type.Literal("crash"),
119
+ Type.Literal("unsafe_scope"),
120
+ Type.Literal("stalled"),
121
+ Type.Literal("config_invalid"),
122
+ Type.Literal("internal"),
123
+ ]);
124
+ export const FlowReasonClassSchema = Type.Union([FlowFailureClassSchema, Type.Literal("size_underestimated")]);
125
+ export const FlowProfileRefSchema = StrictObject({
126
+ id: FlowIdSchema,
127
+ version: FlowPositiveIntSchema,
128
+ digest: FlowDigestSchema,
129
+ });
130
+ /** True when `sha256Hex(text)` equals `expected`. A missing, throwing or non-string hasher is a constant error. */
131
+ export function verifyFlowDigest(hasher, text, expected) {
132
+ let actual;
133
+ try {
134
+ actual = hasher.sha256Hex(text);
135
+ }
136
+ catch {
137
+ throw new FlowContractError("digest_unverifiable");
138
+ }
139
+ if (typeof actual !== "string")
140
+ throw new FlowContractError("digest_unverifiable");
141
+ return actual === expected;
142
+ }
143
+ const MAX_SNAPSHOT_DEPTH = 16;
144
+ const MAX_SNAPSHOT_NODES = 10_000;
145
+ const MAX_SNAPSHOT_STRING = 65_536;
146
+ function isIndexKey(key, length) {
147
+ const index = Number(key);
148
+ return Number.isInteger(index) && index >= 0 && index < length && String(index) === key;
149
+ }
150
+ function snapshotValue(value, depth, budget) {
151
+ budget.nodes += 1;
152
+ if (depth > MAX_SNAPSHOT_DEPTH || budget.nodes > MAX_SNAPSHOT_NODES)
153
+ throw new Error("bounds");
154
+ if (value === null || typeof value === "boolean")
155
+ return value;
156
+ if (typeof value === "string") {
157
+ if (value.length > MAX_SNAPSHOT_STRING || !value.isWellFormed())
158
+ throw new Error("string");
159
+ return value;
160
+ }
161
+ if (typeof value === "number") {
162
+ if (!Number.isFinite(value))
163
+ throw new Error("number");
164
+ return value;
165
+ }
166
+ if (typeof value !== "object")
167
+ throw new Error("type");
168
+ if (Array.isArray(value)) {
169
+ const length = value.length;
170
+ if (length > MAX_SNAPSHOT_NODES)
171
+ throw new Error("bounds");
172
+ for (const key of Reflect.ownKeys(value)) {
173
+ if (typeof key !== "string" || (key !== "length" && !isIndexKey(key, length)))
174
+ throw new Error("array");
175
+ }
176
+ const out = [];
177
+ for (let index = 0; index < length; index += 1) {
178
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
179
+ if (descriptor === undefined || !("value" in descriptor))
180
+ throw new Error("array");
181
+ out.push(snapshotValue(descriptor.value, depth + 1, budget));
182
+ }
183
+ return out;
184
+ }
185
+ const proto = Object.getPrototypeOf(value);
186
+ if (proto !== null && Object.getPrototypeOf(proto) !== null)
187
+ throw new Error("prototype");
188
+ const out = {};
189
+ for (const key of Reflect.ownKeys(value)) {
190
+ if (typeof key !== "string" || !key.isWellFormed())
191
+ throw new Error("key");
192
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
193
+ if (descriptor === undefined || !("value" in descriptor) || descriptor.enumerable !== true)
194
+ throw new Error("property");
195
+ if (descriptor.value === undefined)
196
+ continue; // JSON semantics: an undefined property is absent
197
+ Object.defineProperty(out, key, {
198
+ value: snapshotValue(descriptor.value, depth + 1, budget),
199
+ enumerable: true,
200
+ writable: true,
201
+ configurable: true,
202
+ });
203
+ }
204
+ return out;
205
+ }
206
+ /**
207
+ * Plain-data snapshot of untrusted input: own enumerable data properties only (accessors, symbols, exotic and
208
+ * non-plain objects are rejected), bounded depth/size, finite numbers, well-formed strings. Each property is read
209
+ * exactly once, so the snapshot cannot change after it has been validated.
210
+ */
211
+ export function snapshotFlowData(input) {
212
+ return snapshotValue(input, 0, { nodes: 0 });
213
+ }
214
+ /**
215
+ * Snapshot `input`, validate the snapshot, return the snapshot. Anything thrown while inspecting hostile input
216
+ * (getters, proxy traps, forged FlowContractError objects) is discarded and replaced by a NEW constant `code` error;
217
+ * the thrown value is never read, compared or rethrown. Only our own validation, after the snapshot exists, may throw.
218
+ */
219
+ export function parseFlowData(code, input, validate) {
220
+ const snapshot = trySnapshotFlowData(code, input);
221
+ if (!validate(snapshot))
222
+ throw new FlowContractError(code);
223
+ return snapshot;
224
+ }
225
+ /** `snapshotFlowData` whose every failure is a new `FlowContractError(code)`; the thrown value is not inspected. */
226
+ export function trySnapshotFlowData(code, input) {
227
+ try {
228
+ return snapshotFlowData(input);
229
+ }
230
+ catch {
231
+ throw new FlowContractError(code);
232
+ }
233
+ }
234
+ export function compileFlow(schema) {
235
+ return Compile(schema);
236
+ }
237
+ export function modelKey(ref) {
238
+ return `${ref.provider}\u0000${ref.model}`;
239
+ }
240
+ export function hasDuplicateModels(refs) {
241
+ return new Set(refs.map(modelKey)).size !== refs.length;
242
+ }
243
+ /**
244
+ * Canonical JSON text (RFC 8785 JCS subset): object keys sorted by UTF-16 code unit, no whitespace, `undefined`
245
+ * properties omitted, numbers as ECMAScript Number toString (`JSON.stringify`; -0 prints 0, 1e21 prints 1e+21).
246
+ * Lone surrogates (keys or strings), non-finite numbers and non-JSON values are rejected with a constant error.
247
+ */
248
+ export function canonicalFlowJson(value) {
249
+ if (value === null || typeof value === "boolean")
250
+ return JSON.stringify(value);
251
+ if (typeof value === "string") {
252
+ if (!value.isWellFormed())
253
+ throw new FlowContractError("canonical_invalid");
254
+ return JSON.stringify(value);
255
+ }
256
+ if (typeof value === "number") {
257
+ if (!Number.isFinite(value))
258
+ throw new FlowContractError("canonical_invalid");
259
+ return JSON.stringify(value);
260
+ }
261
+ if (Array.isArray(value))
262
+ return `[${value.map(canonicalFlowJson).join(",")}]`;
263
+ if (typeof value !== "object")
264
+ throw new FlowContractError("canonical_invalid");
265
+ const record = value;
266
+ const parts = [];
267
+ for (const key of Object.keys(record).sort()) {
268
+ if (!key.isWellFormed())
269
+ throw new FlowContractError("canonical_invalid");
270
+ if (record[key] === undefined)
271
+ continue;
272
+ parts.push(`${JSON.stringify(key)}:${canonicalFlowJson(record[key])}`);
273
+ }
274
+ return `{${parts.join(",")}}`;
275
+ }
276
+ /** Canonical text of `doc` without the property `omit`. */
277
+ export function canonicalWithout(doc, omit) {
278
+ const copy = { ...doc };
279
+ delete copy[omit];
280
+ return canonicalFlowJson(copy);
281
+ }
282
+ //# sourceMappingURL=flow-common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-common.js","sourceRoot":"","sources":["../src/flow-common.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Static, type TLiteral, type TSchema, type TUnion, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\n\n/**\n * Shared primitives for the `./flow-*` contract subpaths. TypeBox-only and browser-safe: no node:* imports,\n * no hashing (callers inject `sha256Hex`), no free-text string types.\n */\n\nexport const FLOW_ID_PATTERN = \"^[a-z0-9][a-z0-9_.:-]{0,127}$\";\n/** Model ids may be provider-qualified: lowercase, at most one `/`, no empty segment, <= 128 chars in total. */\nexport const FLOW_MODEL_ID_PATTERN = \"^(?=.{1,128}$)[a-z0-9][a-z0-9_.:-]*(?:/[a-z0-9][a-z0-9_.:-]*)?$\";\nexport const FLOW_DIGEST_PATTERN = \"^[a-f0-9]{64}$\";\nconst FLOW_TS_PATTERN =\n\t\"^\\\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\\\d|3[01])T(?:[01]\\\\d|2[0-3]):[0-5]\\\\d:[0-5]\\\\d(?:\\\\.\\\\d{1,9})?(?:Z|[+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)$\";\n\nexport const FLOW_CONTRACT_ERROR_CODES = {\n\tprofile_invalid: \"Invalid flow profile\",\n\tadmission_invalid: \"Invalid flow run admission\",\n\tevent_invalid: \"Invalid flow event\",\n\tcommand_invalid: \"Invalid flow control command\",\n\treceipt_invalid: \"Invalid flow control receipt\",\n\tstatus_invalid: \"Invalid flow status\",\n\tdigest_mismatch: \"Flow digest mismatch\",\n\tdigest_unverifiable: \"Flow digest cannot be verified\",\n\tcanonical_invalid: \"Flow data cannot be canonicalized\",\n\treceipt_conflict: \"Flow receipt conflicts with the first receipt\",\n} as const;\nexport type FlowContractErrorCode = keyof typeof FLOW_CONTRACT_ERROR_CODES;\n\n/** Messages are constants chosen by contract code from `code`; rejected input is never echoed. */\nexport class FlowContractError extends Error {\n\treadonly code: FlowContractErrorCode;\n\tconstructor(code: FlowContractErrorCode) {\n\t\tsuper(FLOW_CONTRACT_ERROR_CODES[code]);\n\t\tthis.name = \"FlowContractError\";\n\t\tthis.code = code;\n\t}\n}\n\n/** Union of string literals with a precise Static type (a mapped array would infer `never`). */\nexport const lit = <const T extends readonly string[]>(...values: T) =>\n\tType.Union(values.map((value) => Type.Literal(value))) as unknown as TUnion<{\n\t\t-readonly [K in keyof T]: TLiteral<T[K]>;\n\t}>;\n\nexport const StrictObject = <const T extends Parameters<typeof Type.Object>[0]>(properties: T) =>\n\tType.Object(properties, { additionalProperties: false });\n\nexport const FlowIdSchema = Type.String({ pattern: FLOW_ID_PATTERN });\nexport const FlowDigestSchema = Type.String({ pattern: FLOW_DIGEST_PATTERN });\nconst DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] as const;\n\n/** Pattern-valid timestamps only: rejects impossible calendar dates such as 2026-02-31 or a non-leap Feb 29. */\nfunction isRealCalendarDate(timestamp: string): boolean {\n\tconst year = Number(timestamp.slice(0, 4));\n\tconst month = Number(timestamp.slice(5, 7));\n\tconst day = Number(timestamp.slice(8, 10));\n\tconst leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n\treturn day <= (month === 2 && leap ? 29 : (DAYS_IN_MONTH[month - 1] ?? 0));\n}\n\nexport const FlowTimestampSchema = Type.Refine(Type.String({ pattern: FLOW_TS_PATTERN }), isRealCalendarDate);\nexport const FlowMicrousdSchema = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });\nexport const FlowCountSchema = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });\nexport const FlowPositiveIntSchema = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });\n\nexport const FlowModelRefSchema = StrictObject({\n\tprovider: FlowIdSchema,\n\tmodel: Type.String({ pattern: FLOW_MODEL_ID_PATTERN }),\n});\nexport type FlowModelRef = Static<typeof FlowModelRefSchema>;\n\nexport const FLOW_ROLES = [\n\t\"scout\",\n\t\"worker\",\n\t\"implementer\",\n\t\"lead\",\n\t\"lead_large\",\n\t\"architect\",\n\t\"reviewer\",\n\t\"security_reviewer\",\n\t\"qa\",\n\t\"triage\",\n] as const;\nexport type FlowRole = (typeof FLOW_ROLES)[number];\nexport const FlowRoleSchema = Type.Union([\n\tType.Literal(\"scout\"),\n\tType.Literal(\"worker\"),\n\tType.Literal(\"implementer\"),\n\tType.Literal(\"lead\"),\n\tType.Literal(\"lead_large\"),\n\tType.Literal(\"architect\"),\n\tType.Literal(\"reviewer\"),\n\tType.Literal(\"security_reviewer\"),\n\tType.Literal(\"qa\"),\n\tType.Literal(\"triage\"),\n]);\n\n/** `Partial<Record<FlowRole, T>>` as a strict object. */\nexport const partialByRole = <T extends TSchema>(schema: T) =>\n\tStrictObject({\n\t\tscout: Type.Optional(schema),\n\t\tworker: Type.Optional(schema),\n\t\timplementer: Type.Optional(schema),\n\t\tlead: Type.Optional(schema),\n\t\tlead_large: Type.Optional(schema),\n\t\tarchitect: Type.Optional(schema),\n\t\treviewer: Type.Optional(schema),\n\t\tsecurity_reviewer: Type.Optional(schema),\n\t\tqa: Type.Optional(schema),\n\t\ttriage: Type.Optional(schema),\n\t});\n\nexport const FLOW_SIZES = [\"small\", \"medium\", \"large\"] as const;\nexport type FlowSize = (typeof FLOW_SIZES)[number];\nexport const FlowSizeSchema = Type.Union([Type.Literal(\"small\"), Type.Literal(\"medium\"), Type.Literal(\"large\")]);\n\nexport const FLOW_FAILURE_CLASSES = [\n\t\"provider_error\",\n\t\"rate_limit\",\n\t\"timeout\",\n\t\"check_failed\",\n\t\"review_rejected\",\n\t\"qa_failed\",\n\t\"budget_stop\",\n\t\"cancelled\",\n\t\"crash\",\n\t\"unsafe_scope\",\n\t\"stalled\",\n\t\"config_invalid\",\n\t\"internal\",\n] as const;\nexport type FlowFailureClass = (typeof FLOW_FAILURE_CLASSES)[number];\nexport const FlowFailureClassSchema = Type.Union([\n\tType.Literal(\"provider_error\"),\n\tType.Literal(\"rate_limit\"),\n\tType.Literal(\"timeout\"),\n\tType.Literal(\"check_failed\"),\n\tType.Literal(\"review_rejected\"),\n\tType.Literal(\"qa_failed\"),\n\tType.Literal(\"budget_stop\"),\n\tType.Literal(\"cancelled\"),\n\tType.Literal(\"crash\"),\n\tType.Literal(\"unsafe_scope\"),\n\tType.Literal(\"stalled\"),\n\tType.Literal(\"config_invalid\"),\n\tType.Literal(\"internal\"),\n]);\n\nexport const FlowReasonClassSchema = Type.Union([FlowFailureClassSchema, Type.Literal(\"size_underestimated\")]);\nexport type FlowReasonClass = Static<typeof FlowReasonClassSchema>;\n\nexport const FlowProfileRefSchema = StrictObject({\n\tid: FlowIdSchema,\n\tversion: FlowPositiveIntSchema,\n\tdigest: FlowDigestSchema,\n});\nexport type FlowProfileRefV1 = Static<typeof FlowProfileRefSchema>;\n\n/** Hasher injected by the host; keeps contracts free of node:crypto. Required by every digest-verifying parser. */\nexport type FlowDigestHasher = { sha256Hex: (text: string) => string };\n\n/** True when `sha256Hex(text)` equals `expected`. A missing, throwing or non-string hasher is a constant error. */\nexport function verifyFlowDigest(hasher: FlowDigestHasher, text: string, expected: string): boolean {\n\tlet actual: unknown;\n\ttry {\n\t\tactual = hasher.sha256Hex(text);\n\t} catch {\n\t\tthrow new FlowContractError(\"digest_unverifiable\");\n\t}\n\tif (typeof actual !== \"string\") throw new FlowContractError(\"digest_unverifiable\");\n\treturn actual === expected;\n}\n\nconst MAX_SNAPSHOT_DEPTH = 16;\nconst MAX_SNAPSHOT_NODES = 10_000;\nconst MAX_SNAPSHOT_STRING = 65_536;\n\nfunction isIndexKey(key: string, length: number): boolean {\n\tconst index = Number(key);\n\treturn Number.isInteger(index) && index >= 0 && index < length && String(index) === key;\n}\n\nfunction snapshotValue(value: unknown, depth: number, budget: { nodes: number }): unknown {\n\tbudget.nodes += 1;\n\tif (depth > MAX_SNAPSHOT_DEPTH || budget.nodes > MAX_SNAPSHOT_NODES) throw new Error(\"bounds\");\n\tif (value === null || typeof value === \"boolean\") return value;\n\tif (typeof value === \"string\") {\n\t\tif (value.length > MAX_SNAPSHOT_STRING || !value.isWellFormed()) throw new Error(\"string\");\n\t\treturn value;\n\t}\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) throw new Error(\"number\");\n\t\treturn value;\n\t}\n\tif (typeof value !== \"object\") throw new Error(\"type\");\n\tif (Array.isArray(value)) {\n\t\tconst length = value.length;\n\t\tif (length > MAX_SNAPSHOT_NODES) throw new Error(\"bounds\");\n\t\tfor (const key of Reflect.ownKeys(value)) {\n\t\t\tif (typeof key !== \"string\" || (key !== \"length\" && !isIndexKey(key, length))) throw new Error(\"array\");\n\t\t}\n\t\tconst out: unknown[] = [];\n\t\tfor (let index = 0; index < length; index += 1) {\n\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, String(index));\n\t\t\tif (descriptor === undefined || !(\"value\" in descriptor)) throw new Error(\"array\");\n\t\t\tout.push(snapshotValue(descriptor.value, depth + 1, budget));\n\t\t}\n\t\treturn out;\n\t}\n\tconst proto = Object.getPrototypeOf(value);\n\tif (proto !== null && Object.getPrototypeOf(proto) !== null) throw new Error(\"prototype\");\n\tconst out: Record<string, unknown> = {};\n\tfor (const key of Reflect.ownKeys(value)) {\n\t\tif (typeof key !== \"string\" || !key.isWellFormed()) throw new Error(\"key\");\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\tif (descriptor === undefined || !(\"value\" in descriptor) || descriptor.enumerable !== true)\n\t\t\tthrow new Error(\"property\");\n\t\tif (descriptor.value === undefined) continue; // JSON semantics: an undefined property is absent\n\t\tObject.defineProperty(out, key, {\n\t\t\tvalue: snapshotValue(descriptor.value, depth + 1, budget),\n\t\t\tenumerable: true,\n\t\t\twritable: true,\n\t\t\tconfigurable: true,\n\t\t});\n\t}\n\treturn out;\n}\n\n/**\n * Plain-data snapshot of untrusted input: own enumerable data properties only (accessors, symbols, exotic and\n * non-plain objects are rejected), bounded depth/size, finite numbers, well-formed strings. Each property is read\n * exactly once, so the snapshot cannot change after it has been validated.\n */\nexport function snapshotFlowData(input: unknown): unknown {\n\treturn snapshotValue(input, 0, { nodes: 0 });\n}\n\n/**\n * Snapshot `input`, validate the snapshot, return the snapshot. Anything thrown while inspecting hostile input\n * (getters, proxy traps, forged FlowContractError objects) is discarded and replaced by a NEW constant `code` error;\n * the thrown value is never read, compared or rethrown. Only our own validation, after the snapshot exists, may throw.\n */\nexport function parseFlowData<T>(\n\tcode: FlowContractErrorCode,\n\tinput: unknown,\n\tvalidate: (snapshot: unknown) => snapshot is T,\n): T {\n\tconst snapshot = trySnapshotFlowData(code, input);\n\tif (!validate(snapshot)) throw new FlowContractError(code);\n\treturn snapshot;\n}\n\n/** `snapshotFlowData` whose every failure is a new `FlowContractError(code)`; the thrown value is not inspected. */\nexport function trySnapshotFlowData(code: FlowContractErrorCode, input: unknown): unknown {\n\ttry {\n\t\treturn snapshotFlowData(input);\n\t} catch {\n\t\tthrow new FlowContractError(code);\n\t}\n}\n\nexport function compileFlow<T extends TSchema>(schema: T) {\n\treturn Compile(schema);\n}\n\nexport function modelKey(ref: FlowModelRef): string {\n\treturn `${ref.provider}\\u0000${ref.model}`;\n}\n\nexport function hasDuplicateModels(refs: readonly FlowModelRef[]): boolean {\n\treturn new Set(refs.map(modelKey)).size !== refs.length;\n}\n\n/**\n * Canonical JSON text (RFC 8785 JCS subset): object keys sorted by UTF-16 code unit, no whitespace, `undefined`\n * properties omitted, numbers as ECMAScript Number toString (`JSON.stringify`; -0 prints 0, 1e21 prints 1e+21).\n * Lone surrogates (keys or strings), non-finite numbers and non-JSON values are rejected with a constant error.\n */\nexport function canonicalFlowJson(value: unknown): string {\n\tif (value === null || typeof value === \"boolean\") return JSON.stringify(value);\n\tif (typeof value === \"string\") {\n\t\tif (!value.isWellFormed()) throw new FlowContractError(\"canonical_invalid\");\n\t\treturn JSON.stringify(value);\n\t}\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) throw new FlowContractError(\"canonical_invalid\");\n\t\treturn JSON.stringify(value);\n\t}\n\tif (Array.isArray(value)) return `[${value.map(canonicalFlowJson).join(\",\")}]`;\n\tif (typeof value !== \"object\") throw new FlowContractError(\"canonical_invalid\");\n\tconst record = value as Record<string, unknown>;\n\tconst parts: string[] = [];\n\tfor (const key of Object.keys(record).sort()) {\n\t\tif (!key.isWellFormed()) throw new FlowContractError(\"canonical_invalid\");\n\t\tif (record[key] === undefined) continue;\n\t\tparts.push(`${JSON.stringify(key)}:${canonicalFlowJson(record[key])}`);\n\t}\n\treturn `{${parts.join(\",\")}}`;\n}\n\n/** Canonical text of `doc` without the property `omit`. */\nexport function canonicalWithout(doc: object, omit: string): string {\n\tconst copy: Record<string, unknown> = { ...doc };\n\tdelete copy[omit];\n\treturn canonicalFlowJson(copy);\n}\n"]}
@@ -0,0 +1,107 @@
1
+ import { type Static, Type } from "typebox";
2
+ import type { FlowEventV1 } from "./flow-events.ts";
3
+ export declare const FLOW_MESSAGE_MAX_LENGTH = 16000;
4
+ export declare const FlowControlCommandV1Schema: Type.TUnion<[Type.TObject<{
5
+ readonly id: Type.TString;
6
+ readonly type: Type.TLiteral<"flow_status">;
7
+ readonly runId: Type.TOptional<Type.TString>;
8
+ }>, Type.TObject<{
9
+ readonly id: Type.TString;
10
+ readonly type: Type.TLiteral<"flow_pause">;
11
+ readonly runId: Type.TString;
12
+ }>, Type.TObject<{
13
+ readonly id: Type.TString;
14
+ readonly type: Type.TLiteral<"flow_resume">;
15
+ readonly runId: Type.TString;
16
+ }>, Type.TObject<{
17
+ readonly id: Type.TString;
18
+ readonly type: Type.TLiteral<"flow_cancel">;
19
+ readonly runId: Type.TString;
20
+ readonly reason: Type.TUnion<[Type.TLiteral<"user">, Type.TLiteral<"budget">, Type.TLiteral<"shutdown">, Type.TLiteral<"kill_switch">]>;
21
+ }>, Type.TObject<{
22
+ readonly id: Type.TString;
23
+ readonly type: Type.TLiteral<"flow_message">;
24
+ readonly runId: Type.TString;
25
+ readonly messageId: Type.TString;
26
+ readonly text: Type.TString;
27
+ readonly target: Type.TOptional<Type.TUnion<[Type.TObject<{
28
+ readonly taskId: Type.TString;
29
+ }>, Type.TLiteral<"lead">]>>;
30
+ }>]>;
31
+ export type FlowControlCommandV1 = Static<typeof FlowControlCommandV1Schema>;
32
+ export declare const FlowControlReceiptV1Schema: Type.TObject<{
33
+ readonly type: Type.TLiteral<"response">;
34
+ readonly id: Type.TString;
35
+ readonly command: Type.TUnion<[Type.TLiteral<"flow_status">, Type.TLiteral<"flow_pause">, Type.TLiteral<"flow_resume">, Type.TLiteral<"flow_cancel">]>;
36
+ readonly state: Type.TUnion<[Type.TLiteral<"accepted">, Type.TLiteral<"applied">, Type.TLiteral<"rejected">]>;
37
+ readonly reason: Type.TOptional<Type.TUnion<[Type.TUnion<[Type.TLiteral<"provider_error">, Type.TLiteral<"rate_limit">, Type.TLiteral<"timeout">, Type.TLiteral<"check_failed">, Type.TLiteral<"review_rejected">, Type.TLiteral<"qa_failed">, Type.TLiteral<"budget_stop">, Type.TLiteral<"cancelled">, Type.TLiteral<"crash">, Type.TLiteral<"unsafe_scope">, Type.TLiteral<"stalled">, Type.TLiteral<"config_invalid">, Type.TLiteral<"internal">]>, Type.TLiteral<"size_underestimated">]>>;
38
+ readonly terminationConfirmed: Type.TOptional<Type.TBoolean>;
39
+ }>;
40
+ export type FlowControlReceiptV1 = Static<typeof FlowControlReceiptV1Schema>;
41
+ /** `messageId` is carried so message receipts can be de-duplicated by it. */
42
+ export declare const FlowMessageReceiptV1Schema: Type.TObject<{
43
+ readonly type: Type.TLiteral<"response">;
44
+ readonly id: Type.TString;
45
+ readonly command: Type.TLiteral<"flow_message">;
46
+ readonly messageId: Type.TString;
47
+ readonly state: Type.TUnion<[Type.TLiteral<"accepted">, Type.TLiteral<"applied">, Type.TLiteral<"rejected">]>;
48
+ readonly reason: Type.TOptional<Type.TUnion<[Type.TUnion<[Type.TLiteral<"provider_error">, Type.TLiteral<"rate_limit">, Type.TLiteral<"timeout">, Type.TLiteral<"check_failed">, Type.TLiteral<"review_rejected">, Type.TLiteral<"qa_failed">, Type.TLiteral<"budget_stop">, Type.TLiteral<"cancelled">, Type.TLiteral<"crash">, Type.TLiteral<"unsafe_scope">, Type.TLiteral<"stalled">, Type.TLiteral<"config_invalid">, Type.TLiteral<"internal">]>, Type.TLiteral<"size_underestimated">]>>;
49
+ readonly disposition: Type.TUnion<[Type.TLiteral<"live">, Type.TLiteral<"queued">, Type.TLiteral<"rejected">]>;
50
+ readonly deliveredTo: Type.TOptional<Type.TObject<{
51
+ readonly taskId: Type.TString;
52
+ readonly role: Type.TUnion<[Type.TLiteral<"scout">, Type.TLiteral<"worker">, Type.TLiteral<"implementer">, Type.TLiteral<"lead">, Type.TLiteral<"lead_large">, Type.TLiteral<"architect">, Type.TLiteral<"reviewer">, Type.TLiteral<"security_reviewer">, Type.TLiteral<"qa">, Type.TLiteral<"triage">]>;
53
+ }>>;
54
+ }>;
55
+ export type FlowMessageReceiptV1 = Static<typeof FlowMessageReceiptV1Schema>;
56
+ export declare const FlowStatusV1Schema: Type.TObject<{
57
+ readonly runId: Type.TString;
58
+ readonly mode: Type.TUnion<[Type.TLiteral<"auto">, Type.TLiteral<"direct">]>;
59
+ readonly size: Type.TUnion<[Type.TLiteral<"small">, Type.TLiteral<"medium">, Type.TLiteral<"large">]>;
60
+ readonly state: Type.TUnion<[Type.TLiteral<"starting">, Type.TLiteral<"running">, Type.TLiteral<"paused">, Type.TLiteral<"cancelling">, Type.TLiteral<"settled">]>;
61
+ readonly stage: Type.TUnion<[Type.TLiteral<"sizing">, Type.TLiteral<"planning">, Type.TLiteral<"implementing">, Type.TLiteral<"reviewing">, Type.TLiteral<"qa">, Type.TLiteral<"settling">]>;
62
+ readonly agents: Type.TArray<Type.TObject<{
63
+ readonly taskId: Type.TString;
64
+ readonly role: Type.TUnion<[Type.TLiteral<"scout">, Type.TLiteral<"worker">, Type.TLiteral<"implementer">, Type.TLiteral<"lead">, Type.TLiteral<"lead_large">, Type.TLiteral<"architect">, Type.TLiteral<"reviewer">, Type.TLiteral<"security_reviewer">, Type.TLiteral<"qa">, Type.TLiteral<"triage">]>;
65
+ readonly model: Type.TObject<{
66
+ readonly provider: Type.TString;
67
+ readonly model: Type.TString;
68
+ }>;
69
+ readonly state: Type.TUnion<[Type.TLiteral<"pending">, Type.TLiteral<"running">, Type.TLiteral<"succeeded">, Type.TLiteral<"failed">, Type.TLiteral<"cancelled">]>;
70
+ }>>;
71
+ readonly budgetPct: Type.TNumber;
72
+ readonly warnings: Type.TArray<Type.TUnion<[Type.TUnion<[Type.TLiteral<"provider_error">, Type.TLiteral<"rate_limit">, Type.TLiteral<"timeout">, Type.TLiteral<"check_failed">, Type.TLiteral<"review_rejected">, Type.TLiteral<"qa_failed">, Type.TLiteral<"budget_stop">, Type.TLiteral<"cancelled">, Type.TLiteral<"crash">, Type.TLiteral<"unsafe_scope">, Type.TLiteral<"stalled">, Type.TLiteral<"config_invalid">, Type.TLiteral<"internal">]>, Type.TLiteral<"size_underestimated">]>>;
73
+ }>;
74
+ export type FlowStatusV1 = Static<typeof FlowStatusV1Schema>;
75
+ /** Pushed frames (HC -> host). */
76
+ export type FlowEventFrameV1 = {
77
+ type: "flow_event";
78
+ event: FlowEventV1;
79
+ };
80
+ export type FlowStatusFrameV1 = {
81
+ type: "flow_status";
82
+ status: FlowStatusV1;
83
+ };
84
+ /** Each parser validates a plain-data snapshot of `input` and returns the snapshot (never the caller's object). */
85
+ export declare function parseFlowControlCommandV1(input: unknown): FlowControlCommandV1;
86
+ export declare function parseFlowControlReceiptV1(input: unknown): FlowControlReceiptV1;
87
+ export declare function parseFlowMessageReceiptV1(input: unknown): FlowMessageReceiptV1;
88
+ export declare function parseFlowStatusV1(input: unknown): FlowStatusV1;
89
+ export type FlowReceiptV1 = FlowControlReceiptV1 | FlowMessageReceiptV1;
90
+ /**
91
+ * True when `next` is an allowed repeat of `prev`. Same command id => identical receipt. Same messageId
92
+ * (message receipts) => identical receipt apart from `id`. Unrelated receipts return false. Conflicts throw.
93
+ */
94
+ export declare function checkFlowReceiptIdempotency(prev: FlowReceiptV1, next: FlowReceiptV1): boolean;
95
+ export type FlowReceiptLedger = {
96
+ /**
97
+ * First receipt wins. A repeat (same command id, or same messageId under a new command id) returns a copy of the
98
+ * first receipt and binds the new command id to it; a conflicting repeat throws. Receipts are validated, stored
99
+ * frozen, and returned as deep copies.
100
+ */
101
+ record(receipt: FlowReceiptV1): {
102
+ replayed: boolean;
103
+ receipt: FlowReceiptV1;
104
+ };
105
+ };
106
+ export declare function createFlowReceiptLedger(): FlowReceiptLedger;
107
+ //# sourceMappingURL=flow-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-control.d.ts","sourceRoot":"","sources":["../src/flow-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAe5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;IAkBrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI7E,eAAO,MAAM,0BAA0B;;;;;;;EAOrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE7E,6EAA6E;AAC7E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;EASrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE7E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE7D,kCAAkC;AAClC,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAO9E,mHAAmH;AACnH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAS9E;AAUD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAE9E;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAI9E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAE9D;AAED,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAWxE;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAO7F;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC/B;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE,CAAC;CAC9E,CAAC;AAUF,wBAAgB,uBAAuB,IAAI,iBAAiB,CAgC3D"}
@@ -0,0 +1,144 @@
1
+ import { Type } from "typebox";
2
+ import { canonicalFlowJson, compileFlow, FlowContractError, FlowIdSchema, FlowModelRefSchema, FlowReasonClassSchema, FlowRoleSchema, FlowSizeSchema, lit, parseFlowData, StrictObject, trySnapshotFlowData, } from "./flow-common.js";
3
+ export const FLOW_MESSAGE_MAX_LENGTH = 16_000;
4
+ export const FlowControlCommandV1Schema = Type.Union([
5
+ StrictObject({ id: FlowIdSchema, type: Type.Literal("flow_status"), runId: Type.Optional(FlowIdSchema) }),
6
+ StrictObject({ id: FlowIdSchema, type: Type.Literal("flow_pause"), runId: FlowIdSchema }),
7
+ StrictObject({ id: FlowIdSchema, type: Type.Literal("flow_resume"), runId: FlowIdSchema }),
8
+ StrictObject({
9
+ id: FlowIdSchema,
10
+ type: Type.Literal("flow_cancel"),
11
+ runId: FlowIdSchema,
12
+ reason: lit("user", "budget", "shutdown", "kill_switch"),
13
+ }),
14
+ StrictObject({
15
+ id: FlowIdSchema,
16
+ type: Type.Literal("flow_message"),
17
+ runId: FlowIdSchema,
18
+ messageId: FlowIdSchema,
19
+ text: Type.String({ minLength: 1, maxLength: FLOW_MESSAGE_MAX_LENGTH }),
20
+ target: Type.Optional(Type.Union([StrictObject({ taskId: FlowIdSchema }), Type.Literal("lead")])),
21
+ }),
22
+ ]);
23
+ const State = lit("accepted", "applied", "rejected");
24
+ export const FlowControlReceiptV1Schema = StrictObject({
25
+ type: Type.Literal("response"),
26
+ id: FlowIdSchema,
27
+ command: lit("flow_status", "flow_pause", "flow_resume", "flow_cancel"),
28
+ state: State,
29
+ reason: Type.Optional(FlowReasonClassSchema),
30
+ terminationConfirmed: Type.Optional(Type.Boolean()),
31
+ });
32
+ /** `messageId` is carried so message receipts can be de-duplicated by it. */
33
+ export const FlowMessageReceiptV1Schema = StrictObject({
34
+ type: Type.Literal("response"),
35
+ id: FlowIdSchema,
36
+ command: Type.Literal("flow_message"),
37
+ messageId: FlowIdSchema,
38
+ state: State,
39
+ reason: Type.Optional(FlowReasonClassSchema),
40
+ disposition: lit("live", "queued", "rejected"),
41
+ deliveredTo: Type.Optional(StrictObject({ taskId: FlowIdSchema, role: FlowRoleSchema })),
42
+ });
43
+ export const FlowStatusV1Schema = StrictObject({
44
+ runId: FlowIdSchema,
45
+ mode: lit("auto", "direct"),
46
+ size: FlowSizeSchema,
47
+ state: lit("starting", "running", "paused", "cancelling", "settled"),
48
+ stage: lit("sizing", "planning", "implementing", "reviewing", "qa", "settling"),
49
+ agents: Type.Array(StrictObject({
50
+ taskId: FlowIdSchema,
51
+ role: FlowRoleSchema,
52
+ model: FlowModelRefSchema,
53
+ state: lit("pending", "running", "succeeded", "failed", "cancelled"),
54
+ }), { maxItems: 64 }),
55
+ budgetPct: Type.Number({ minimum: 0, maximum: 100 }),
56
+ warnings: Type.Array(FlowReasonClassSchema, { maxItems: 16 }),
57
+ });
58
+ const CommandValidator = compileFlow(FlowControlCommandV1Schema);
59
+ const ControlReceiptValidator = compileFlow(FlowControlReceiptV1Schema);
60
+ const MessageReceiptValidator = compileFlow(FlowMessageReceiptV1Schema);
61
+ const StatusValidator = compileFlow(FlowStatusV1Schema);
62
+ /** Each parser validates a plain-data snapshot of `input` and returns the snapshot (never the caller's object). */
63
+ export function parseFlowControlCommandV1(input) {
64
+ // TypeBox may count maxLength in code points; the contract is UTF-16 code units (String.length), like rpc.ts.
65
+ return parseFlowData("command_invalid", input, (value) => CommandValidator.Check(value) &&
66
+ (value.type !== "flow_message" || value.text.length <= FLOW_MESSAGE_MAX_LENGTH));
67
+ }
68
+ function isControlReceipt(value) {
69
+ // terminationConfirmed is meaningful for cancel only.
70
+ return (ControlReceiptValidator.Check(value) &&
71
+ (value.terminationConfirmed === undefined || value.command === "flow_cancel"));
72
+ }
73
+ export function parseFlowControlReceiptV1(input) {
74
+ return parseFlowData("receipt_invalid", input, isControlReceipt);
75
+ }
76
+ export function parseFlowMessageReceiptV1(input) {
77
+ return parseFlowData("receipt_invalid", input, (value) => MessageReceiptValidator.Check(value));
78
+ }
79
+ export function parseFlowStatusV1(input) {
80
+ return parseFlowData("status_invalid", input, (value) => StatusValidator.Check(value));
81
+ }
82
+ function withId(receipt, id) {
83
+ return { ...receipt, id };
84
+ }
85
+ function withoutId(receipt) {
86
+ const { id: _id, ...rest } = receipt;
87
+ return canonicalFlowJson(rest);
88
+ }
89
+ /**
90
+ * True when `next` is an allowed repeat of `prev`. Same command id => identical receipt. Same messageId
91
+ * (message receipts) => identical receipt apart from `id`. Unrelated receipts return false. Conflicts throw.
92
+ */
93
+ export function checkFlowReceiptIdempotency(prev, next) {
94
+ const sameId = prev.id === next.id;
95
+ const sameMessage = "messageId" in prev && "messageId" in next && prev.messageId === next.messageId;
96
+ if (!sameId && !sameMessage)
97
+ return false;
98
+ const identical = sameId ? canonicalFlowJson(prev) === canonicalFlowJson(next) : withoutId(prev) === withoutId(next);
99
+ if (!identical)
100
+ throw new FlowContractError("receipt_conflict");
101
+ return true;
102
+ }
103
+ function deepFreeze(value) {
104
+ if (typeof value === "object" && value !== null) {
105
+ for (const child of Object.values(value))
106
+ deepFreeze(child);
107
+ Object.freeze(value);
108
+ }
109
+ return value;
110
+ }
111
+ export function createFlowReceiptLedger() {
112
+ // Every command id seen (including replay aliases) maps to the first receipt it was bound to.
113
+ const byCommand = new Map();
114
+ const byMessage = new Map();
115
+ return {
116
+ record(input) {
117
+ // Snapshot first: the discriminant is read from the snapshot, never from the caller's object.
118
+ const snapshot = trySnapshotFlowData("receipt_invalid", input);
119
+ const next = snapshot?.command === "flow_message"
120
+ ? parseFlowMessageReceiptV1(snapshot)
121
+ : parseFlowControlReceiptV1(snapshot);
122
+ const priors = [
123
+ byCommand.get(next.id),
124
+ "messageId" in next ? byMessage.get(next.messageId) : undefined,
125
+ ].filter((prior) => prior !== undefined);
126
+ for (const prior of priors) {
127
+ if (canonicalFlowJson(withId(prior, next.id)) !== canonicalFlowJson(next)) {
128
+ throw new FlowContractError("receipt_conflict");
129
+ }
130
+ }
131
+ const first = priors[0];
132
+ if (first !== undefined) {
133
+ byCommand.set(next.id, first);
134
+ return { replayed: true, receipt: structuredClone(first) };
135
+ }
136
+ const stored = deepFreeze(next);
137
+ byCommand.set(stored.id, stored);
138
+ if ("messageId" in stored)
139
+ byMessage.set(stored.messageId, stored);
140
+ return { replayed: false, receipt: structuredClone(stored) };
141
+ },
142
+ };
143
+ }
144
+ //# sourceMappingURL=flow-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-control.js","sourceRoot":"","sources":["../src/flow-control.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport {\n\tcanonicalFlowJson,\n\tcompileFlow,\n\tFlowContractError,\n\tFlowIdSchema,\n\tFlowModelRefSchema,\n\tFlowReasonClassSchema,\n\tFlowRoleSchema,\n\tFlowSizeSchema,\n\tlit,\n\tparseFlowData,\n\tStrictObject,\n\ttrySnapshotFlowData,\n} from \"./flow-common.ts\";\nimport type { FlowEventV1 } from \"./flow-events.ts\";\n\nexport const FLOW_MESSAGE_MAX_LENGTH = 16_000;\n\nexport const FlowControlCommandV1Schema = Type.Union([\n\tStrictObject({ id: FlowIdSchema, type: Type.Literal(\"flow_status\"), runId: Type.Optional(FlowIdSchema) }),\n\tStrictObject({ id: FlowIdSchema, type: Type.Literal(\"flow_pause\"), runId: FlowIdSchema }),\n\tStrictObject({ id: FlowIdSchema, type: Type.Literal(\"flow_resume\"), runId: FlowIdSchema }),\n\tStrictObject({\n\t\tid: FlowIdSchema,\n\t\ttype: Type.Literal(\"flow_cancel\"),\n\t\trunId: FlowIdSchema,\n\t\treason: lit(\"user\", \"budget\", \"shutdown\", \"kill_switch\"),\n\t}),\n\tStrictObject({\n\t\tid: FlowIdSchema,\n\t\ttype: Type.Literal(\"flow_message\"),\n\t\trunId: FlowIdSchema,\n\t\tmessageId: FlowIdSchema,\n\t\ttext: Type.String({ minLength: 1, maxLength: FLOW_MESSAGE_MAX_LENGTH }),\n\t\ttarget: Type.Optional(Type.Union([StrictObject({ taskId: FlowIdSchema }), Type.Literal(\"lead\")])),\n\t}),\n]);\nexport type FlowControlCommandV1 = Static<typeof FlowControlCommandV1Schema>;\n\nconst State = lit(\"accepted\", \"applied\", \"rejected\");\n\nexport const FlowControlReceiptV1Schema = StrictObject({\n\ttype: Type.Literal(\"response\"),\n\tid: FlowIdSchema,\n\tcommand: lit(\"flow_status\", \"flow_pause\", \"flow_resume\", \"flow_cancel\"),\n\tstate: State,\n\treason: Type.Optional(FlowReasonClassSchema),\n\tterminationConfirmed: Type.Optional(Type.Boolean()),\n});\nexport type FlowControlReceiptV1 = Static<typeof FlowControlReceiptV1Schema>;\n\n/** `messageId` is carried so message receipts can be de-duplicated by it. */\nexport const FlowMessageReceiptV1Schema = StrictObject({\n\ttype: Type.Literal(\"response\"),\n\tid: FlowIdSchema,\n\tcommand: Type.Literal(\"flow_message\"),\n\tmessageId: FlowIdSchema,\n\tstate: State,\n\treason: Type.Optional(FlowReasonClassSchema),\n\tdisposition: lit(\"live\", \"queued\", \"rejected\"),\n\tdeliveredTo: Type.Optional(StrictObject({ taskId: FlowIdSchema, role: FlowRoleSchema })),\n});\nexport type FlowMessageReceiptV1 = Static<typeof FlowMessageReceiptV1Schema>;\n\nexport const FlowStatusV1Schema = StrictObject({\n\trunId: FlowIdSchema,\n\tmode: lit(\"auto\", \"direct\"),\n\tsize: FlowSizeSchema,\n\tstate: lit(\"starting\", \"running\", \"paused\", \"cancelling\", \"settled\"),\n\tstage: lit(\"sizing\", \"planning\", \"implementing\", \"reviewing\", \"qa\", \"settling\"),\n\tagents: Type.Array(\n\t\tStrictObject({\n\t\t\ttaskId: FlowIdSchema,\n\t\t\trole: FlowRoleSchema,\n\t\t\tmodel: FlowModelRefSchema,\n\t\t\tstate: lit(\"pending\", \"running\", \"succeeded\", \"failed\", \"cancelled\"),\n\t\t}),\n\t\t{ maxItems: 64 },\n\t),\n\tbudgetPct: Type.Number({ minimum: 0, maximum: 100 }),\n\twarnings: Type.Array(FlowReasonClassSchema, { maxItems: 16 }),\n});\nexport type FlowStatusV1 = Static<typeof FlowStatusV1Schema>;\n\n/** Pushed frames (HC -> host). */\nexport type FlowEventFrameV1 = { type: \"flow_event\"; event: FlowEventV1 };\nexport type FlowStatusFrameV1 = { type: \"flow_status\"; status: FlowStatusV1 };\n\nconst CommandValidator = compileFlow(FlowControlCommandV1Schema);\nconst ControlReceiptValidator = compileFlow(FlowControlReceiptV1Schema);\nconst MessageReceiptValidator = compileFlow(FlowMessageReceiptV1Schema);\nconst StatusValidator = compileFlow(FlowStatusV1Schema);\n\n/** Each parser validates a plain-data snapshot of `input` and returns the snapshot (never the caller's object). */\nexport function parseFlowControlCommandV1(input: unknown): FlowControlCommandV1 {\n\t// TypeBox may count maxLength in code points; the contract is UTF-16 code units (String.length), like rpc.ts.\n\treturn parseFlowData(\n\t\t\"command_invalid\",\n\t\tinput,\n\t\t(value): value is FlowControlCommandV1 =>\n\t\t\tCommandValidator.Check(value) &&\n\t\t\t(value.type !== \"flow_message\" || value.text.length <= FLOW_MESSAGE_MAX_LENGTH),\n\t);\n}\n\nfunction isControlReceipt(value: unknown): value is FlowControlReceiptV1 {\n\t// terminationConfirmed is meaningful for cancel only.\n\treturn (\n\t\tControlReceiptValidator.Check(value) &&\n\t\t(value.terminationConfirmed === undefined || value.command === \"flow_cancel\")\n\t);\n}\n\nexport function parseFlowControlReceiptV1(input: unknown): FlowControlReceiptV1 {\n\treturn parseFlowData(\"receipt_invalid\", input, isControlReceipt);\n}\n\nexport function parseFlowMessageReceiptV1(input: unknown): FlowMessageReceiptV1 {\n\treturn parseFlowData(\"receipt_invalid\", input, (value): value is FlowMessageReceiptV1 =>\n\t\tMessageReceiptValidator.Check(value),\n\t);\n}\n\nexport function parseFlowStatusV1(input: unknown): FlowStatusV1 {\n\treturn parseFlowData(\"status_invalid\", input, (value): value is FlowStatusV1 => StatusValidator.Check(value));\n}\n\nexport type FlowReceiptV1 = FlowControlReceiptV1 | FlowMessageReceiptV1;\n\nfunction withId(receipt: FlowReceiptV1, id: string): FlowReceiptV1 {\n\treturn { ...receipt, id };\n}\n\nfunction withoutId(receipt: FlowReceiptV1): string {\n\tconst { id: _id, ...rest } = receipt;\n\treturn canonicalFlowJson(rest);\n}\n\n/**\n * True when `next` is an allowed repeat of `prev`. Same command id => identical receipt. Same messageId\n * (message receipts) => identical receipt apart from `id`. Unrelated receipts return false. Conflicts throw.\n */\nexport function checkFlowReceiptIdempotency(prev: FlowReceiptV1, next: FlowReceiptV1): boolean {\n\tconst sameId = prev.id === next.id;\n\tconst sameMessage = \"messageId\" in prev && \"messageId\" in next && prev.messageId === next.messageId;\n\tif (!sameId && !sameMessage) return false;\n\tconst identical = sameId ? canonicalFlowJson(prev) === canonicalFlowJson(next) : withoutId(prev) === withoutId(next);\n\tif (!identical) throw new FlowContractError(\"receipt_conflict\");\n\treturn true;\n}\n\nexport type FlowReceiptLedger = {\n\t/**\n\t * First receipt wins. A repeat (same command id, or same messageId under a new command id) returns a copy of the\n\t * first receipt and binds the new command id to it; a conflicting repeat throws. Receipts are validated, stored\n\t * frozen, and returned as deep copies.\n\t */\n\trecord(receipt: FlowReceiptV1): { replayed: boolean; receipt: FlowReceiptV1 };\n};\n\nfunction deepFreeze<T>(value: T): T {\n\tif (typeof value === \"object\" && value !== null) {\n\t\tfor (const child of Object.values(value)) deepFreeze(child);\n\t\tObject.freeze(value);\n\t}\n\treturn value;\n}\n\nexport function createFlowReceiptLedger(): FlowReceiptLedger {\n\t// Every command id seen (including replay aliases) maps to the first receipt it was bound to.\n\tconst byCommand = new Map<string, FlowReceiptV1>();\n\tconst byMessage = new Map<string, FlowReceiptV1>();\n\treturn {\n\t\trecord(input) {\n\t\t\t// Snapshot first: the discriminant is read from the snapshot, never from the caller's object.\n\t\t\tconst snapshot = trySnapshotFlowData(\"receipt_invalid\", input);\n\t\t\tconst next =\n\t\t\t\t(snapshot as { command?: unknown } | null)?.command === \"flow_message\"\n\t\t\t\t\t? parseFlowMessageReceiptV1(snapshot)\n\t\t\t\t\t: parseFlowControlReceiptV1(snapshot);\n\t\t\tconst priors = [\n\t\t\t\tbyCommand.get(next.id),\n\t\t\t\t\"messageId\" in next ? byMessage.get(next.messageId) : undefined,\n\t\t\t].filter((prior): prior is FlowReceiptV1 => prior !== undefined);\n\t\t\tfor (const prior of priors) {\n\t\t\t\tif (canonicalFlowJson(withId(prior, next.id)) !== canonicalFlowJson(next)) {\n\t\t\t\t\tthrow new FlowContractError(\"receipt_conflict\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst first = priors[0];\n\t\t\tif (first !== undefined) {\n\t\t\t\tbyCommand.set(next.id, first);\n\t\t\t\treturn { replayed: true, receipt: structuredClone(first) };\n\t\t\t}\n\t\t\tconst stored = deepFreeze(next);\n\t\t\tbyCommand.set(stored.id, stored);\n\t\t\tif (\"messageId\" in stored) byMessage.set(stored.messageId, stored);\n\t\t\treturn { replayed: false, receipt: structuredClone(stored) };\n\t\t},\n\t};\n}\n"]}