@skill-harness/core 0.16.0 → 0.18.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 (93) hide show
  1. package/README.md +1 -2
  2. package/dist/adapters/types.d.ts +0 -12
  3. package/dist/capture-trace-types.d.ts +0 -96
  4. package/dist/capture-trace-types.js +0 -4
  5. package/dist/command-cost.d.ts +1 -1
  6. package/dist/command-cost.js +0 -5
  7. package/dist/execution-trace.js +1 -1
  8. package/dist/index.d.ts +3 -23
  9. package/dist/index.js +3 -23
  10. package/dist/instruction-coverage.d.ts +0 -7
  11. package/dist/instruction-coverage.js +5 -7
  12. package/dist/journal.d.ts +0 -8
  13. package/dist/metrics.d.ts +18 -0
  14. package/dist/metrics.js +28 -0
  15. package/dist/redaction.d.ts +6 -0
  16. package/dist/redaction.js +55 -0
  17. package/dist/regate.js +6 -71
  18. package/dist/regrade.d.ts +1 -3
  19. package/dist/regrade.js +1 -21
  20. package/dist/report.d.ts +1 -15
  21. package/dist/report.js +1 -13
  22. package/dist/reps.d.ts +1 -2
  23. package/dist/reps.js +0 -2
  24. package/dist/results.d.ts +0 -9
  25. package/dist/results.js +2 -19
  26. package/dist/run.js +11 -72
  27. package/dist/seeded.d.ts +1 -7
  28. package/dist/seeded.js +5 -11
  29. package/dist/sources.js +4 -9
  30. package/dist/spec.d.ts +0 -13
  31. package/dist/spec.js +6 -48
  32. package/dist/trajectory-events.d.ts +58 -0
  33. package/dist/trajectory-events.js +119 -0
  34. package/package.json +1 -1
  35. package/schemas/results-v2.schema.json +376 -75
  36. package/schemas/specification-v1.schema.json +128 -174
  37. package/dist/adjudication.d.ts +0 -219
  38. package/dist/adjudication.js +0 -422
  39. package/dist/adoption.d.ts +0 -128
  40. package/dist/adoption.js +0 -102
  41. package/dist/affected.d.ts +0 -88
  42. package/dist/affected.js +0 -222
  43. package/dist/capture.d.ts +0 -193
  44. package/dist/capture.js +0 -344
  45. package/dist/comparison.d.ts +0 -97
  46. package/dist/comparison.js +0 -189
  47. package/dist/factory-calibration.d.ts +0 -63
  48. package/dist/factory-calibration.js +0 -115
  49. package/dist/intervention-results.d.ts +0 -6
  50. package/dist/intervention-results.js +0 -52
  51. package/dist/intervention.d.ts +0 -123
  52. package/dist/intervention.js +0 -218
  53. package/dist/investigation.d.ts +0 -94
  54. package/dist/investigation.js +0 -148
  55. package/dist/judge-agreement.d.ts +0 -19
  56. package/dist/judge-agreement.js +0 -31
  57. package/dist/qualification-capture.d.ts +0 -44
  58. package/dist/qualification-capture.js +0 -163
  59. package/dist/qualification-config.d.ts +0 -233
  60. package/dist/qualification-config.js +0 -694
  61. package/dist/qualification-lock.d.ts +0 -15
  62. package/dist/qualification-lock.js +0 -87
  63. package/dist/qualification-oauth-directory.d.ts +0 -79
  64. package/dist/qualification-oauth-directory.js +0 -444
  65. package/dist/qualification-panel-store.d.ts +0 -14
  66. package/dist/qualification-panel-store.js +0 -188
  67. package/dist/qualification-panels.d.ts +0 -99
  68. package/dist/qualification-panels.js +0 -91
  69. package/dist/qualification-process.d.ts +0 -13
  70. package/dist/qualification-process.js +0 -190
  71. package/dist/qualification-runner.d.ts +0 -203
  72. package/dist/qualification-runner.js +0 -2037
  73. package/dist/qualification-store.d.ts +0 -187
  74. package/dist/qualification-store.js +0 -833
  75. package/dist/qualification-validation.d.ts +0 -12
  76. package/dist/qualification-validation.js +0 -8
  77. package/dist/screen.d.ts +0 -31
  78. package/dist/screen.js +0 -105
  79. package/dist/trajectory-gates.d.ts +0 -137
  80. package/dist/trajectory-gates.js +0 -576
  81. package/dist/trusted-host-supervision.d.ts +0 -25
  82. package/dist/trusted-host-supervision.js +0 -94
  83. package/dist/work-capture-schema.d.ts +0 -38
  84. package/dist/work-capture-schema.js +0 -33
  85. package/dist/work-capture.d.ts +0 -43
  86. package/dist/work-capture.js +0 -81
  87. package/dist/work-signals.d.ts +0 -55
  88. package/dist/work-signals.js +0 -71
  89. package/schemas/qualification-cell-result-v1.schema.json +0 -32
  90. package/schemas/qualification-config-v1.schema.json +0 -244
  91. package/schemas/qualification-invocation-request-v1.schema.json +0 -59
  92. package/schemas/qualification-judge-panel-result-v1.schema.json +0 -42
  93. package/schemas/trajectory-event-v1.schema.json +0 -80
@@ -1,576 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { parsePredicate, testPredicate } from "./trace-gates.js";
3
- /** Adapter-neutral event contract used by objective trajectory assertions. */
4
- export const LEGACY_TRAJECTORY_EVENT_VERSION = "1.0";
5
- export const TRAJECTORY_EVENT_VERSION = "1.1";
6
- export const TRAJECTORY_ASSERT_VERSION = "1.0";
7
- export function trajectoryEventsSha256(events) {
8
- return createHash("sha256").update(stableStringify(events)).digest("hex");
9
- }
10
- export function serializeTrajectoryEvents(events) {
11
- return events.map((event) => JSON.stringify(event)).join("\n") + (events.length ? "\n" : "");
12
- }
13
- export function deserializeTrajectoryEvents(text) {
14
- const out = [];
15
- try {
16
- for (const line of text.split("\n").filter((entry) => entry.trim())) {
17
- const event = JSON.parse(line);
18
- if (validateEvent(event) !== null)
19
- return null;
20
- out.push(event);
21
- }
22
- }
23
- catch {
24
- return null;
25
- }
26
- if (!out.length)
27
- return null;
28
- const sequences = out.map((event) => event.seq).sort((a, b) => a - b);
29
- if (new Set(sequences).size !== out.length || sequences.some((seq, index) => seq !== index + 1))
30
- return null;
31
- return out;
32
- }
33
- /** Evaluate a closed, declarative assertion set over saved normalized events. */
34
- export function evaluateTrajectoryGates(assert, input) {
35
- const assertions = [];
36
- const events = [...input].sort((a, b) => a.seq - b.seq);
37
- const invalid = input.map((event) => ({ event, problem: validateEvent(event) })).find((entry) => entry.problem !== null);
38
- const sequences = [...input.map((event) => event.seq)].sort((a, b) => a - b);
39
- const duplicateSeq = new Set(sequences).size !== input.length;
40
- const nonContiguous = sequences.some((seq, index) => seq !== index + 1);
41
- if (invalid || duplicateSeq || nonContiguous) {
42
- assertions.push({
43
- kind: "evidence",
44
- status: "ERROR",
45
- detail: invalid
46
- ? `normalized event evidence is invalid at sequence ${String(invalid.event.seq)}: ${invalid.problem}`
47
- : duplicateSeq
48
- ? "normalized event evidence has duplicate sequence numbers"
49
- : "normalized event evidence sequence must be contiguous from 1",
50
- });
51
- }
52
- for (const required of assert.require ?? []) {
53
- const hits = matching(events, required);
54
- const min = required.count?.min ?? 1;
55
- const max = required.count?.max;
56
- const ok = hits.length >= min && (max === undefined || hits.length <= max);
57
- assertions.push({
58
- kind: "require_event",
59
- status: ok ? "PASS" : "FAIL",
60
- detail: ok
61
- ? `required event \`${required.event}\` occurred ${hits.length} time(s)`
62
- : `required event \`${required.event}\` expected ${bounds(min, max)}, saw ${hits.length}`,
63
- });
64
- }
65
- for (const forbidden of assert.forbid ?? []) {
66
- const hits = matching(events, forbidden);
67
- assertions.push({
68
- kind: "forbid_event",
69
- status: hits.length ? "FAIL" : "PASS",
70
- detail: hits.length
71
- ? `forbidden event \`${forbidden.event}\` occurred at sequence(s) ${hits.map((event) => event.seq).join(", ")}`
72
- : `forbidden event \`${forbidden.event}\` did not occur`,
73
- });
74
- }
75
- for (const chain of assert.ordered ?? []) {
76
- let cursor = -Infinity;
77
- const found = [];
78
- for (const selector of chain) {
79
- const next = matching(events, selector).find((event) => event.seq > cursor);
80
- if (!next)
81
- break;
82
- found.push(next);
83
- cursor = next.seq;
84
- }
85
- const ok = found.length === chain.length;
86
- assertions.push({
87
- kind: "ordered_events",
88
- status: ok ? "PASS" : "FAIL",
89
- detail: ok
90
- ? `ordered events occurred at sequences ${found.map((event) => event.seq).join(" < ")}`
91
- : `ordered trajectory broke at \`${chain[found.length]?.event ?? "unknown"}\` after sequence ${cursor === -Infinity ? "start" : cursor}`,
92
- });
93
- }
94
- for (const correlation of assert.correlate ?? []) {
95
- const left = selected(events, correlation.left);
96
- const right = selected(events, correlation.right);
97
- if (!left.length || !right.length) {
98
- assertions.push({ kind: "correlation", status: "FAIL", detail: `correlation needs \`${correlation.left.event}\` and \`${correlation.right.event}\`` });
99
- continue;
100
- }
101
- for (const l of left)
102
- for (const r of right) {
103
- const checked = compareFields(l, r, correlation.same ?? [], correlation.different ?? []);
104
- if (checked.error) {
105
- assertions.push({ kind: "correlation", status: "ERROR", detail: checked.error });
106
- continue;
107
- }
108
- const ordered = correlation.order === undefined || (correlation.order === "before" ? l.seq < r.seq : l.seq > r.seq);
109
- assertions.push({
110
- kind: "correlation",
111
- status: checked.ok && ordered ? "PASS" : "FAIL",
112
- detail: checked.ok && ordered
113
- ? `\`${correlation.left.event}\` and \`${correlation.right.event}\` satisfy identity/order correlation`
114
- : `\`${correlation.left.event}\` and \`${correlation.right.event}\` violate ${!checked.ok ? "identity" : `${correlation.order} ordering`} correlation`,
115
- });
116
- }
117
- }
118
- for (const freshness of assert.freshness ?? []) {
119
- const subjects = selected(events, freshness.subject);
120
- if (!subjects.length) {
121
- assertions.push({ kind: "freshness", status: "FAIL", detail: `freshness subject \`${freshness.subject.event}\` is missing` });
122
- continue;
123
- }
124
- for (const subject of subjects) {
125
- let floor = -Infinity;
126
- let failure = null;
127
- let error = null;
128
- for (const selector of freshness.after) {
129
- const candidates = matching(events, selector);
130
- const correlated = [];
131
- for (const candidate of candidates) {
132
- const checked = compareFields(subject, candidate, freshness.same ?? [], []);
133
- if (checked.error)
134
- error ??= checked.error;
135
- else if (checked.ok)
136
- correlated.push(candidate);
137
- }
138
- const anchor = correlated.at(-1);
139
- if (!anchor)
140
- failure = `freshness anchor \`${selector.event}\` is missing for the correlated identity`;
141
- else
142
- floor = Math.max(floor, anchor.seq);
143
- }
144
- assertions.push(error
145
- ? { kind: "freshness", status: "ERROR", detail: error }
146
- : failure
147
- ? { kind: "freshness", status: "FAIL", detail: failure }
148
- : subject.seq > floor
149
- ? { kind: "freshness", status: "PASS", detail: `\`${freshness.subject.event}\` at ${subject.seq} is newer than freshness floor ${floor}` }
150
- : { kind: "freshness", status: "FAIL", detail: `\`${freshness.subject.event}\` at ${subject.seq} is stale; freshness floor is ${floor}` });
151
- }
152
- }
153
- for (const uniqueness of assert.unique ?? []) {
154
- const hits = matching(events, uniqueness.events);
155
- for (const field of uniqueness.fields) {
156
- const values = hits.map((event) => fieldValue(event, field));
157
- const missing = values.findIndex((value) => value === undefined || value === null || value === "");
158
- if (missing >= 0) {
159
- assertions.push({ kind: "unique", status: "ERROR", detail: `uniqueness field \`${field}\` is missing on \`${uniqueness.events.event}\` at sequence ${hits[missing].seq}` });
160
- }
161
- else {
162
- const duplicates = values.filter((value, index) => values.findIndex((other) => deepEqual(value, other)) !== index);
163
- assertions.push({
164
- kind: "unique",
165
- status: duplicates.length ? "FAIL" : "PASS",
166
- detail: duplicates.length ? `\`${field}\` was reused across independent \`${uniqueness.events.event}\` events` : `\`${field}\` is unique across ${hits.length} event(s)`,
167
- });
168
- }
169
- }
170
- }
171
- for (const rule of assert.forbid_after ?? []) {
172
- const anchors = selected(events, rule.anchor);
173
- if (!anchors.length) {
174
- assertions.push({
175
- kind: "forbid_after",
176
- status: rule.anchor_optional ? "PASS" : "FAIL",
177
- detail: rule.anchor_optional ? `optional anchor \`${rule.anchor.event}\` did not occur` : `anchor \`${rule.anchor.event}\` is missing`,
178
- });
179
- continue;
180
- }
181
- for (const anchor of anchors) {
182
- let violations = 0;
183
- let error = null;
184
- for (const selector of rule.forbidden) {
185
- for (const candidate of matching(events, selector).filter((event) => event.seq > anchor.seq)) {
186
- const checked = compareFields(anchor, candidate, rule.same ?? [], []);
187
- if (checked.error)
188
- error ??= checked.error;
189
- else if (checked.ok)
190
- violations++;
191
- }
192
- }
193
- assertions.push(error
194
- ? { kind: "forbid_after", status: "ERROR", detail: error }
195
- : {
196
- kind: "forbid_after",
197
- status: violations ? "FAIL" : "PASS",
198
- detail: violations
199
- ? `${violations} forbidden mutation event(s) occurred after \`${rule.anchor.event}\` for the same identity`
200
- : `no forbidden mutation followed \`${rule.anchor.event}\``,
201
- });
202
- }
203
- }
204
- for (const approval of assert.approvals ?? []) {
205
- const grants = selected(events, approval.grant);
206
- const uses = approval.use.select ? selected(events, approval.use) : matching(events, approval.use);
207
- if (!grants.length || !uses.length) {
208
- assertions.push({ kind: "approval", status: "FAIL", detail: "approval grant/use evidence is incomplete" });
209
- continue;
210
- }
211
- for (const grant of grants) {
212
- const matchingUses = [];
213
- let error = null;
214
- let usedBeforeGrant = false;
215
- const identityFields = [...new Set([
216
- ...(approval.same ?? []),
217
- "approval.id",
218
- "approval.capability",
219
- ...(approval.scopes ? ["approval.scope"] : []),
220
- ...(approval.sources ? ["approval.source"] : []),
221
- ])];
222
- for (const use of uses) {
223
- const checked = compareFields(grant, use, identityFields, []);
224
- if (checked.error)
225
- error ??= checked.error;
226
- else if (checked.ok && use.seq <= grant.seq)
227
- usedBeforeGrant = true;
228
- else if (checked.ok)
229
- matchingUses.push(use);
230
- }
231
- const scope = grant.approval?.scope;
232
- const source = grant.approval?.source;
233
- if (error) {
234
- assertions.push({ kind: "approval", status: "ERROR", detail: error });
235
- continue;
236
- }
237
- if (approval.scopes && (!scope || !approval.scopes.includes(scope))) {
238
- assertions.push({ kind: "approval", status: scope ? "FAIL" : "ERROR", detail: scope ? `approval scope \`${scope}\` is not allowed` : "approval scope is missing" });
239
- continue;
240
- }
241
- if (approval.sources && (!source || !approval.sources.includes(source))) {
242
- assertions.push({ kind: "approval", status: source ? "FAIL" : "ERROR", detail: source ? `approval source \`${source}\` is not allowed` : "approval source is missing" });
243
- continue;
244
- }
245
- if (usedBeforeGrant) {
246
- assertions.push({ kind: "approval", status: "FAIL", detail: "approval was used before its grant event" });
247
- continue;
248
- }
249
- if (matchingUses.length === 0) {
250
- assertions.push({ kind: "approval", status: "FAIL", detail: "no approval use matches the granted scope/identity" });
251
- continue;
252
- }
253
- if (approval.max_uses !== undefined && matchingUses.length > approval.max_uses) {
254
- assertions.push({ kind: "approval", status: "FAIL", detail: `approval was used ${matchingUses.length} times (max ${approval.max_uses})` });
255
- continue;
256
- }
257
- if (approval.unexpired) {
258
- const approved = grant.approval?.approved_at ?? grant.at;
259
- const expires = grant.approval?.expires_at;
260
- const timestampsInvalid = !approved || !expires || !validDate(approved) || !validDate(expires) ||
261
- matchingUses.some((use) => !validDate(use.approval?.used_at ?? use.at));
262
- if (timestampsInvalid) {
263
- assertions.push({ kind: "approval", status: "ERROR", detail: "approval grant/expiry/use timestamp is missing or invalid" });
264
- continue;
265
- }
266
- const invalidUse = matchingUses.find((use) => {
267
- const used = use.approval?.used_at ?? use.at;
268
- return Date.parse(used) < Date.parse(approved) || Date.parse(used) >= Date.parse(expires);
269
- });
270
- if (Date.parse(approved) >= Date.parse(expires) || invalidUse) {
271
- assertions.push({ kind: "approval", status: "FAIL", detail: "approval was used outside its grant/expiry interval" });
272
- continue;
273
- }
274
- }
275
- assertions.push({ kind: "approval", status: "PASS", detail: `approval was used ${matchingUses.length} time(s) within declared scope and expiry` });
276
- }
277
- }
278
- for (const coverage of assert.coverage ?? []) {
279
- const hits = coverage.events ? matching(events, coverage.events) : events;
280
- const covered = new Set(hits.flatMap((event) => event.requirements ?? []));
281
- const missing = coverage.requirements.filter((requirement) => !covered.has(requirement));
282
- assertions.push({
283
- kind: "coverage",
284
- status: missing.length ? "FAIL" : "PASS",
285
- detail: missing.length ? `missing requirement coverage: ${missing.join(", ")}` : `requirement coverage recorded: ${coverage.requirements.join(", ")}`,
286
- });
287
- }
288
- return {
289
- status: assertions.some((result) => result.status === "ERROR") ? "ERROR" : assertions.some((result) => result.status === "FAIL") ? "FAIL" : "PASS",
290
- event_version: TRAJECTORY_EVENT_VERSION,
291
- events_sha256: trajectoryEventsSha256(events),
292
- assertions,
293
- };
294
- }
295
- // ---------------------------------------------------------------------------
296
- // Parsing
297
- // ---------------------------------------------------------------------------
298
- export function parseTrajectoryAssert(raw, ctx) {
299
- const object = asObject(raw, `${ctx}: \`assert.trajectory\``);
300
- const allowed = new Set(["version", "require", "forbid", "ordered", "correlate", "freshness", "unique", "forbid_after", "approvals", "coverage"]);
301
- rejectUnknown(object, allowed, `${ctx}: \`assert.trajectory\``);
302
- if (object.version !== TRAJECTORY_ASSERT_VERSION)
303
- throw new Error(`${ctx}: \`assert.trajectory.version\` must be ${TRAJECTORY_ASSERT_VERSION}`);
304
- const out = { version: TRAJECTORY_ASSERT_VERSION };
305
- if (object.require !== undefined)
306
- out.require = nonEmptyArray(object.require, `${ctx}: require`).map((value, index) => parseRequired(value, `${ctx}: require[${index}]`));
307
- if (object.forbid !== undefined)
308
- out.forbid = nonEmptyArray(object.forbid, `${ctx}: forbid`).map((value, index) => parseSelector(value, `${ctx}: forbid[${index}]`));
309
- if (object.ordered !== undefined)
310
- out.ordered = nonEmptyArray(object.ordered, `${ctx}: ordered`).map((chain, index) => nonEmptyArray(chain, `${ctx}: ordered[${index}]`).map((value, step) => parseSelector(value, `${ctx}: ordered[${index}][${step}]`)));
311
- if (object.correlate !== undefined)
312
- out.correlate = nonEmptyArray(object.correlate, `${ctx}: correlate`).map((value, index) => parseCorrelation(value, `${ctx}: correlate[${index}]`));
313
- if (object.freshness !== undefined)
314
- out.freshness = nonEmptyArray(object.freshness, `${ctx}: freshness`).map((value, index) => parseFreshness(value, `${ctx}: freshness[${index}]`));
315
- if (object.unique !== undefined)
316
- out.unique = nonEmptyArray(object.unique, `${ctx}: unique`).map((value, index) => parseUnique(value, `${ctx}: unique[${index}]`));
317
- if (object.forbid_after !== undefined)
318
- out.forbid_after = nonEmptyArray(object.forbid_after, `${ctx}: forbid_after`).map((value, index) => parseForbidAfter(value, `${ctx}: forbid_after[${index}]`));
319
- if (object.approvals !== undefined)
320
- out.approvals = nonEmptyArray(object.approvals, `${ctx}: approvals`).map((value, index) => parseApproval(value, `${ctx}: approvals[${index}]`));
321
- if (object.coverage !== undefined)
322
- out.coverage = nonEmptyArray(object.coverage, `${ctx}: coverage`).map((value, index) => parseCoverage(value, `${ctx}: coverage[${index}]`));
323
- if (Object.keys(out).length === 1)
324
- throw new Error(`${ctx}: \`assert.trajectory\` declares no assertions`);
325
- return out;
326
- }
327
- function parseRequired(raw, ctx) {
328
- const object = asObject(raw, ctx);
329
- rejectUnknown(object, new Set(["event", "where", "select", "count"]), ctx);
330
- const out = parseSelector({ event: object.event, ...(object.where === undefined ? {} : { where: object.where }), ...(object.select === undefined ? {} : { select: object.select }) }, ctx);
331
- if (object.count !== undefined)
332
- out.count = parseCount(object.count, `${ctx}.count`);
333
- return out;
334
- }
335
- function parseSelector(raw, ctx) {
336
- const object = asObject(raw, ctx);
337
- rejectUnknown(object, new Set(["event", "where", "select"]), ctx);
338
- if (typeof object.event !== "string" || !object.event.trim())
339
- throw new Error(`${ctx}.event must be a non-empty string`);
340
- const out = { event: object.event };
341
- if (object.where !== undefined) {
342
- const where = asObject(object.where, `${ctx}.where`);
343
- out.where = Object.fromEntries(Object.entries(where).map(([field, value]) => [field, parsePredicate(value, `${ctx}.where.${field}`)]));
344
- }
345
- if (object.select !== undefined) {
346
- if (!["first", "last", "all"].includes(object.select))
347
- throw new Error(`${ctx}.select must be first, last, or all`);
348
- out.select = object.select;
349
- }
350
- return out;
351
- }
352
- function parseCorrelation(raw, ctx) {
353
- const object = asObject(raw, ctx);
354
- rejectUnknown(object, new Set(["left", "right", "same", "different", "order"]), ctx);
355
- const out = { left: parseSelector(object.left, `${ctx}.left`), right: parseSelector(object.right, `${ctx}.right`) };
356
- if (object.same !== undefined)
357
- out.same = stringList(object.same, `${ctx}.same`);
358
- if (object.different !== undefined)
359
- out.different = stringList(object.different, `${ctx}.different`);
360
- if (object.order !== undefined) {
361
- if (object.order !== "before" && object.order !== "after")
362
- throw new Error(`${ctx}.order must be before or after`);
363
- out.order = object.order;
364
- }
365
- if (!out.same?.length && !out.different?.length && !out.order)
366
- throw new Error(`${ctx} declares no relation`);
367
- return out;
368
- }
369
- function parseFreshness(raw, ctx) {
370
- const object = asObject(raw, ctx);
371
- rejectUnknown(object, new Set(["subject", "after", "same"]), ctx);
372
- return {
373
- subject: parseSelector(object.subject, `${ctx}.subject`),
374
- after: nonEmptyArray(object.after, `${ctx}.after`).map((value, index) => parseSelector(value, `${ctx}.after[${index}]`)),
375
- ...(object.same === undefined ? {} : { same: stringList(object.same, `${ctx}.same`) }),
376
- };
377
- }
378
- function parseUnique(raw, ctx) {
379
- const object = asObject(raw, ctx);
380
- rejectUnknown(object, new Set(["events", "fields"]), ctx);
381
- return { events: parseSelector(object.events, `${ctx}.events`), fields: stringList(object.fields, `${ctx}.fields`) };
382
- }
383
- function parseForbidAfter(raw, ctx) {
384
- const object = asObject(raw, ctx);
385
- rejectUnknown(object, new Set(["anchor", "forbidden", "same", "anchor_optional"]), ctx);
386
- if (object.anchor_optional !== undefined && typeof object.anchor_optional !== "boolean")
387
- throw new Error(`${ctx}.anchor_optional must be boolean`);
388
- return {
389
- anchor: parseSelector(object.anchor, `${ctx}.anchor`),
390
- forbidden: nonEmptyArray(object.forbidden, `${ctx}.forbidden`).map((value, index) => parseSelector(value, `${ctx}.forbidden[${index}]`)),
391
- ...(object.same === undefined ? {} : { same: stringList(object.same, `${ctx}.same`) }),
392
- ...(object.anchor_optional === undefined ? {} : { anchor_optional: object.anchor_optional }),
393
- };
394
- }
395
- function parseApproval(raw, ctx) {
396
- const object = asObject(raw, ctx);
397
- rejectUnknown(object, new Set(["grant", "use", "same", "scopes", "sources", "unexpired", "max_uses"]), ctx);
398
- if (object.unexpired !== undefined && typeof object.unexpired !== "boolean")
399
- throw new Error(`${ctx}.unexpired must be boolean`);
400
- if (object.max_uses !== undefined && (!Number.isInteger(object.max_uses) || Number(object.max_uses) < 1))
401
- throw new Error(`${ctx}.max_uses must be a positive integer`);
402
- return {
403
- grant: parseSelector(object.grant, `${ctx}.grant`),
404
- use: parseSelector(object.use, `${ctx}.use`),
405
- ...(object.same === undefined ? {} : { same: stringList(object.same, `${ctx}.same`) }),
406
- ...(object.scopes === undefined ? {} : { scopes: stringList(object.scopes, `${ctx}.scopes`) }),
407
- ...(object.sources === undefined ? {} : { sources: stringList(object.sources, `${ctx}.sources`) }),
408
- ...(object.unexpired === undefined ? {} : { unexpired: object.unexpired }),
409
- ...(object.max_uses === undefined ? {} : { max_uses: Number(object.max_uses) }),
410
- };
411
- }
412
- function parseCoverage(raw, ctx) {
413
- const object = asObject(raw, ctx);
414
- rejectUnknown(object, new Set(["requirements", "events"]), ctx);
415
- return { requirements: stringList(object.requirements, `${ctx}.requirements`), ...(object.events === undefined ? {} : { events: parseSelector(object.events, `${ctx}.events`) }) };
416
- }
417
- function parseCount(raw, ctx) {
418
- const object = asObject(raw, ctx);
419
- rejectUnknown(object, new Set(["min", "max"]), ctx);
420
- const out = {};
421
- for (const key of ["min", "max"]) {
422
- if (object[key] === undefined)
423
- continue;
424
- if (!Number.isInteger(object[key]) || Number(object[key]) < 0)
425
- throw new Error(`${ctx}.${key} must be a non-negative integer`);
426
- out[key] = Number(object[key]);
427
- }
428
- if (out.min !== undefined && out.max !== undefined && out.min > out.max)
429
- throw new Error(`${ctx}.min exceeds max`);
430
- return out;
431
- }
432
- // ---------------------------------------------------------------------------
433
- // Internals
434
- // ---------------------------------------------------------------------------
435
- const V11_EVENT_KEYS = new Set(["execution_id", "parent_execution_id", "task_from_execution_id", "workflow_fact_id", "deadline_at"]);
436
- const EVENT_KEYS = new Set([
437
- "event_version", "seq", "type", "source", "at", "run_id", "task_id", "workspace_id", "context_id", "finding_id",
438
- "parent_id", "child_id", ...V11_EVENT_KEYS,
439
- "phase", "tool", "capability", "requested_capabilities", "effective_capabilities", "refusal_code",
440
- "exit_code", "digests", "approval", "requirements", "attributes",
441
- ]);
442
- const APPROVAL_KEYS = new Set(["id", "capability", "subject", "source", "scope", "approved_at", "expires_at", "used_at"]);
443
- const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
444
- const SHA256_RE = /^[a-fA-F0-9]{64}$/;
445
- const GIT_SHA_RE = /^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/;
446
- const REFUSAL_RE = /^[A-Z][A-Z0-9_]*$/;
447
- /** Runtime counterpart of trajectory-event-v1.schema.json; evidence is rejected before any assertion can consume it. */
448
- function validateEvent(event) {
449
- if (!event || typeof event !== "object" || Array.isArray(event))
450
- return "event must be an object";
451
- const object = event;
452
- const unknown = Object.keys(object).find((key) => !EVENT_KEYS.has(key));
453
- if (unknown)
454
- return `unknown field ${unknown}`;
455
- if (event.event_version !== LEGACY_TRAJECTORY_EVENT_VERSION && event.event_version !== TRAJECTORY_EVENT_VERSION) {
456
- return `unsupported event_version ${String(event.event_version)}`;
457
- }
458
- if (event.event_version === LEGACY_TRAJECTORY_EVENT_VERSION) {
459
- const versionedField = Object.keys(object).find((key) => V11_EVENT_KEYS.has(key));
460
- if (versionedField)
461
- return `${versionedField} requires event_version ${TRAJECTORY_EVENT_VERSION}`;
462
- }
463
- if (!Number.isInteger(event.seq) || event.seq < 1)
464
- return "seq must be a positive integer";
465
- if (typeof event.type !== "string" || !event.type)
466
- return "type must be a non-empty string";
467
- if (typeof event.source !== "string" || !event.source)
468
- return "source must be a non-empty string";
469
- if (event.at !== undefined && !validDate(event.at))
470
- return "at must be an RFC 3339 date-time";
471
- for (const field of ["run_id", "task_id", "workspace_id", "context_id", "finding_id", "parent_id", "child_id", "execution_id", "task_from_execution_id", "workflow_fact_id"]) {
472
- if (event[field] !== undefined && (typeof event[field] !== "string" || !ID_RE.test(event[field])))
473
- return `${field} is not a valid bounded identifier`;
474
- }
475
- if (event.parent_execution_id !== undefined && event.parent_execution_id !== null &&
476
- (typeof event.parent_execution_id !== "string" || !ID_RE.test(event.parent_execution_id)))
477
- return "parent_execution_id is not a valid bounded identifier or null";
478
- if (event.deadline_at !== undefined && !validDate(event.deadline_at))
479
- return "deadline_at must be an RFC 3339 date-time";
480
- for (const field of ["phase", "tool", "capability"]) {
481
- if (event[field] !== undefined && (typeof event[field] !== "string" || !event[field]))
482
- return `${field} must be a non-empty string`;
483
- }
484
- for (const field of ["requested_capabilities", "effective_capabilities", "requirements"]) {
485
- const values = event[field];
486
- if (values !== undefined && (!Array.isArray(values) || values.some((value) => typeof value !== "string" || !value) || new Set(values).size !== values.length)) {
487
- return `${field} must be an array of unique non-empty strings`;
488
- }
489
- }
490
- if (event.refusal_code !== undefined && !REFUSAL_RE.test(event.refusal_code))
491
- return "refusal_code is invalid";
492
- if (event.exit_code !== undefined && !Number.isInteger(event.exit_code))
493
- return "exit_code must be an integer";
494
- if (event.digests !== undefined) {
495
- if (!event.digests || typeof event.digests !== "object" || Array.isArray(event.digests))
496
- return "digests must be an object";
497
- for (const [key, value] of Object.entries(event.digests)) {
498
- if (typeof value !== "string")
499
- return `digests.${key} must be a string`;
500
- if (["plan", "task", "definition"].includes(key) && !SHA256_RE.test(value))
501
- return `digests.${key} must be sha256`;
502
- if (["head", "tree"].includes(key) && !GIT_SHA_RE.test(value))
503
- return `digests.${key} must be a git object id`;
504
- }
505
- }
506
- if (event.approval !== undefined) {
507
- if (!event.approval || typeof event.approval !== "object" || Array.isArray(event.approval))
508
- return "approval must be an object";
509
- const unknownApproval = Object.keys(event.approval).find((key) => !APPROVAL_KEYS.has(key));
510
- if (unknownApproval)
511
- return `approval.${unknownApproval} is unknown`;
512
- for (const [key, value] of Object.entries(event.approval)) {
513
- if (typeof value !== "string" || !value)
514
- return `approval.${key} must be a non-empty string`;
515
- if (key === "id" && !ID_RE.test(value))
516
- return "approval.id is invalid";
517
- if (["approved_at", "expires_at", "used_at"].includes(key) && !validDate(value))
518
- return `approval.${key} must be an RFC 3339 date-time`;
519
- }
520
- }
521
- if (event.attributes !== undefined && (!event.attributes || typeof event.attributes !== "object" || Array.isArray(event.attributes)))
522
- return "attributes must be an object";
523
- return null;
524
- }
525
- function matching(events, selector) {
526
- return events.filter((event) => event.type === selector.event && Object.entries(selector.where ?? {}).every(([field, predicate]) => testPredicate(fieldValue(event, field), predicate)));
527
- }
528
- function selected(events, selector) {
529
- const hits = matching(events, selector);
530
- if (selector.select === "all")
531
- return hits;
532
- return hits.length ? [selector.select === "first" ? hits[0] : hits[hits.length - 1]] : [];
533
- }
534
- function fieldValue(event, field) {
535
- let current = event;
536
- for (const part of field.split(".")) {
537
- if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, part))
538
- return undefined;
539
- current = current[part];
540
- }
541
- return current;
542
- }
543
- function compareFields(left, right, same, different) {
544
- for (const field of [...same, ...different]) {
545
- const l = fieldValue(left, field), r = fieldValue(right, field);
546
- if (l === undefined || l === null || r === undefined || r === null)
547
- return { ok: false, error: `correlation field \`${field}\` is missing at sequence ${l === undefined || l === null ? left.seq : right.seq}` };
548
- }
549
- return { ok: same.every((field) => deepEqual(fieldValue(left, field), fieldValue(right, field))) && different.every((field) => !deepEqual(fieldValue(left, field), fieldValue(right, field))) };
550
- }
551
- function deepEqual(left, right) { return JSON.stringify(left) === JSON.stringify(right); }
552
- function validDate(value) {
553
- if (typeof value !== "string")
554
- return false;
555
- const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.exec(value);
556
- if (!match || !Number.isFinite(Date.parse(value)))
557
- return false;
558
- const [, year, month, day, hour, minute, second] = match.map(Number);
559
- if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59)
560
- return false;
561
- return day >= 1 && day <= new Date(Date.UTC(year, month, 0)).getUTCDate();
562
- }
563
- function bounds(min, max) { return max === undefined ? `at least ${min}` : min === 0 ? `at most ${max}` : `${min}..${max}`; }
564
- function asObject(value, ctx) { if (!value || typeof value !== "object" || Array.isArray(value))
565
- throw new Error(`${ctx} must be a mapping`); return value; }
566
- function nonEmptyArray(value, ctx) { if (!Array.isArray(value) || value.length === 0)
567
- throw new Error(`${ctx} must be a non-empty list`); return value; }
568
- function stringList(value, ctx) { const values = nonEmptyArray(value, ctx); if (values.some((item) => typeof item !== "string" || !item.trim()))
569
- throw new Error(`${ctx} must contain non-empty strings`); if (new Set(values).size !== values.length)
570
- throw new Error(`${ctx} must not contain duplicates`); return values; }
571
- function rejectUnknown(object, allowed, ctx) { const unknown = Object.keys(object).find((key) => !allowed.has(key)); if (unknown)
572
- throw new Error(`${ctx}: unknown key \`${unknown}\``); }
573
- function stableStringify(value) { if (value === null || typeof value !== "object")
574
- return JSON.stringify(value) ?? "null"; if (Array.isArray(value))
575
- return `[${value.map(stableStringify).join(",")}]`; return `{${Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`; }
576
- //# sourceMappingURL=trajectory-gates.js.map
@@ -1,25 +0,0 @@
1
- import type { ChildProcess } from 'node:child_process';
2
- import { type QualificationProcessIdentity } from './qualification-process.js';
3
- export interface TrustedHostSupervisionLimits {
4
- wallMs: number;
5
- settlementMs: number;
6
- maxOutputBytes: number;
7
- signal: AbortSignal;
8
- }
9
- export interface TrustedHostProcessObservation {
10
- outcome: 'completed' | 'failed' | 'aborted' | 'timed-out' | 'output-limit' | 'unknown';
11
- identity: QualificationProcessIdentity;
12
- exitCode: number | null;
13
- signal: NodeJS.Signals | null;
14
- stdoutBytes: number;
15
- stderrBytes: number;
16
- truncated: boolean;
17
- cleanupAttempted: boolean;
18
- acceptance: 'not-assessed';
19
- }
20
- /** Observe an ALREADY admitted, caller-owned detached Linux child, after its spawn event.
21
- * No spawn/authorization/accounting/retry is performed here. The parent event loop must
22
- * remain responsive. Occurrence-safe same-group cleanup is not hostile-code containment,
23
- * survivor/crash recovery, hard aggregate RAM/CPU, or proof of remote provider termination.
24
- * Bytes are discarded, not retained or hashed; the existing worker journal owns artifacts. */
25
- export declare function superviseTrustedHost(child: ChildProcess, limits: TrustedHostSupervisionLimits): Promise<TrustedHostProcessObservation>;