@cleocode/contracts 2026.9.8 → 2026.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/acceptance-gate.test.js +242 -1
- package/dist/__tests__/acceptance-gate.test.js.map +1 -1
- package/dist/__tests__/evidence-atom-schema.test.js +17 -14
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
- package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
- package/dist/__tests__/operations-registry.test.js +18 -0
- package/dist/__tests__/operations-registry.test.js.map +1 -1
- package/dist/acceptance-gate-schema.d.ts +60 -2
- package/dist/acceptance-gate-schema.d.ts.map +1 -1
- package/dist/acceptance-gate-schema.js +273 -1
- package/dist/acceptance-gate-schema.js.map +1 -1
- package/dist/acceptance-gate.d.ts +122 -11
- package/dist/acceptance-gate.d.ts.map +1 -1
- package/dist/acceptance-gate.js +3 -3
- package/dist/audit.d.ts +71 -2
- package/dist/audit.d.ts.map +1 -1
- package/dist/backup-manifest.d.ts +80 -0
- package/dist/backup-manifest.d.ts.map +1 -1
- package/dist/caamp-markers.d.ts +20 -0
- package/dist/caamp-markers.d.ts.map +1 -1
- package/dist/capabilities.d.ts +282 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/changesets.d.ts +4 -4
- package/dist/code-symbol.d.ts +33 -0
- package/dist/code-symbol.d.ts.map +1 -1
- package/dist/daemon/health.d.ts +3 -3
- package/dist/data-accessor.d.ts +15 -0
- package/dist/data-accessor.d.ts.map +1 -1
- package/dist/data-accessor.js.map +1 -1
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +19 -1
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +684 -3
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/docs-taxonomy.d.ts +25 -0
- package/dist/docs-taxonomy.d.ts.map +1 -1
- package/dist/docs-taxonomy.js.map +1 -1
- package/dist/evidence-atom-schema.d.ts +7 -6
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +10 -9
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/graph.d.ts +283 -2
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/invariants/adr-070-orchestration.d.ts.map +1 -1
- package/dist/invariants/adr-070-orchestration.js +1 -1
- package/dist/invariants/adr-070-orchestration.js.map +1 -1
- package/dist/invariants/adr-073-saga.js +2 -2
- package/dist/invariants/adr-073-saga.js.map +1 -1
- package/dist/jobs.d.ts +329 -3
- package/dist/jobs.d.ts.map +1 -1
- package/dist/knowledge-health.d.ts +695 -0
- package/dist/knowledge-health.d.ts.map +1 -0
- package/dist/knowledge-health.js +8 -0
- package/dist/knowledge-health.js.map +1 -0
- package/dist/llm/catalog-schema.d.ts +4 -4
- package/dist/memory/observe.d.ts +4 -2
- package/dist/memory/observe.d.ts.map +1 -1
- package/dist/memory/search.d.ts +4 -0
- package/dist/memory/search.d.ts.map +1 -1
- package/dist/nexus-living-brain-ops.d.ts +28 -3
- package/dist/nexus-living-brain-ops.d.ts.map +1 -1
- package/dist/nexus-tasks-bridge-ops.d.ts +11 -0
- package/dist/nexus-tasks-bridge-ops.d.ts.map +1 -1
- package/dist/operations/admin.d.ts +10 -4
- package/dist/operations/admin.d.ts.map +1 -1
- package/dist/operations/docs.d.ts +138 -0
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js +47 -0
- package/dist/operations/docs.js.map +1 -1
- package/dist/operations/focus.d.ts +7 -0
- package/dist/operations/focus.d.ts.map +1 -1
- package/dist/operations/nexus.d.ts +7 -2
- package/dist/operations/nexus.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.js +31 -3
- package/dist/operations/output-contracts-data.js.map +1 -1
- package/dist/operations/research.d.ts +33 -0
- package/dist/operations/research.d.ts.map +1 -1
- package/dist/operations/tasks.d.ts +107 -4
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js +5 -0
- package/dist/operations/tasks.js.map +1 -1
- package/dist/package-artifact.d.ts +112 -0
- package/dist/package-artifact.d.ts.map +1 -0
- package/dist/package-artifact.js +8 -0
- package/dist/package-artifact.js.map +1 -0
- package/dist/release/evidence-atoms.d.ts +10 -0
- package/dist/release/evidence-atoms.d.ts.map +1 -1
- package/dist/release/evidence-atoms.js +6 -0
- package/dist/release/evidence-atoms.js.map +1 -1
- package/dist/release/plan.d.ts +6 -6
- package/dist/resource-governor.d.ts +114 -0
- package/dist/resource-governor.d.ts.map +1 -1
- package/dist/resource-governor.js.map +1 -1
- package/dist/supervisor-ipc/messages.d.ts +6 -6
- package/dist/task-record.d.ts +25 -4
- package/dist/task-record.d.ts.map +1 -1
- package/dist/task.d.ts +55 -36
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js.map +1 -1
- package/dist/tasks/archive.d.ts +3 -3
- package/dist/templates/manifest.d.ts +1 -1
- package/dist/workgraph.d.ts +5 -3
- package/dist/workgraph.d.ts.map +1 -1
- package/dist/workgraph.js +19 -0
- package/dist/workgraph.js.map +1 -1
- package/package.json +2 -2
- package/schemas/gate-result.schema.json +350 -1
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/acceptance-gate.test.ts +282 -1
- package/src/__tests__/evidence-atom-schema.test.ts +17 -14
- package/src/__tests__/memory-wire-shapes.test.ts +2 -0
- package/src/__tests__/operations-registry.test.ts +26 -0
- package/src/acceptance-gate-schema.ts +320 -14
- package/src/acceptance-gate.ts +133 -11
- package/src/audit.ts +72 -2
- package/src/backup-manifest.ts +88 -0
- package/src/caamp-markers.ts +22 -0
- package/src/capabilities.ts +311 -0
- package/src/code-symbol.ts +37 -0
- package/src/data-accessor.ts +16 -0
- package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +31 -1
- package/src/dispatch/operations-registry.ts +689 -3
- package/src/docs-taxonomy.ts +26 -0
- package/src/evidence-atom-schema.ts +10 -9
- package/src/graph.ts +326 -2
- package/src/index.ts +43 -1
- package/src/invariants/adr-070-orchestration.ts +2 -1
- package/src/invariants/adr-073-saga.ts +2 -2
- package/src/jobs.ts +382 -3
- package/src/knowledge-health.ts +748 -0
- package/src/memory/observe.ts +4 -2
- package/src/memory/search.ts +4 -0
- package/src/nexus-living-brain-ops.ts +34 -3
- package/src/nexus-tasks-bridge-ops.ts +12 -0
- package/src/operations/admin.ts +11 -4
- package/src/operations/docs.ts +153 -0
- package/src/operations/focus.ts +11 -0
- package/src/operations/nexus.ts +7 -1
- package/src/operations/output-contracts-data.ts +33 -3
- package/src/operations/research.ts +36 -0
- package/src/operations/tasks.ts +116 -4
- package/src/package-artifact.ts +127 -0
- package/src/release/evidence-atoms.ts +6 -0
- package/src/resource-governor.ts +132 -0
- package/src/task-record.ts +27 -4
- package/src/task.ts +58 -36
- package/src/workgraph.ts +19 -0
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
+
import type { AcceptanceGateBinding } from './acceptance-gate.js';
|
|
17
|
+
import type { ProcessCaptureResult } from './resource-governor.js';
|
|
16
18
|
|
|
17
19
|
// ─── Base schema ──────────────────────────────────────────────────────────────
|
|
18
20
|
|
|
@@ -79,6 +81,90 @@ export const testGateSchema = gateBaseSchema.extend({
|
|
|
79
81
|
env: z.record(z.string(), z.string()).optional(),
|
|
80
82
|
});
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Validates the structured JSON reporter subset used for minimum test counts.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* Audited against Vitest 4.1.4 JSON output, including nested suites, skipped
|
|
89
|
+
* tests and todos. Jest-compatible producers must satisfy this same shape;
|
|
90
|
+
* this is not a claim that every Jest version or custom reporter is supported.
|
|
91
|
+
* Counts must match individual assertions. Passed tests alone satisfy a gate's
|
|
92
|
+
* minimum; this schema validates structure, not execution provenance or success.
|
|
93
|
+
* Extra reporter metadata is ignored. Runtime callers must bound input bytes
|
|
94
|
+
* and obtain the report from the actual owned process, not a saved report file.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const report = testCountReportSchema.safeParse(JSON.parse(captured.stdout));
|
|
99
|
+
* if (report.success) console.info(report.data.numPassedTests);
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export const testCountReportSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
numTotalTests: z.number().int().nonnegative().safe(),
|
|
105
|
+
numPassedTests: z.number().int().nonnegative().safe(),
|
|
106
|
+
numFailedTests: z.number().int().nonnegative().safe(),
|
|
107
|
+
numPendingTests: z.number().int().nonnegative().safe(),
|
|
108
|
+
numTodoTests: z.number().int().nonnegative().safe(),
|
|
109
|
+
numTotalTestSuites: z.number().int().nonnegative().safe(),
|
|
110
|
+
numPassedTestSuites: z.number().int().nonnegative().safe(),
|
|
111
|
+
numFailedTestSuites: z.number().int().nonnegative().safe(),
|
|
112
|
+
numPendingTestSuites: z.number().int().nonnegative().safe(),
|
|
113
|
+
success: z.boolean(),
|
|
114
|
+
testResults: z.array(
|
|
115
|
+
z.object({
|
|
116
|
+
name: z.string().min(1),
|
|
117
|
+
status: z.enum(['passed', 'failed']),
|
|
118
|
+
assertionResults: z.array(
|
|
119
|
+
z.object({
|
|
120
|
+
fullName: z.string(),
|
|
121
|
+
status: z.enum(['passed', 'failed', 'pending', 'skipped', 'todo']),
|
|
122
|
+
}),
|
|
123
|
+
),
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
})
|
|
127
|
+
.superRefine((report, context) => {
|
|
128
|
+
const assertions = report.testResults.flatMap((file) => file.assertionResults);
|
|
129
|
+
const passed = assertions.filter((test) => test.status === 'passed').length;
|
|
130
|
+
const failed = assertions.filter((test) => test.status === 'failed').length;
|
|
131
|
+
const pending = assertions.filter(
|
|
132
|
+
(test) => test.status === 'pending' || test.status === 'skipped',
|
|
133
|
+
).length;
|
|
134
|
+
const todo = assertions.filter((test) => test.status === 'todo').length;
|
|
135
|
+
if (
|
|
136
|
+
report.numTotalTests !== assertions.length ||
|
|
137
|
+
report.numPassedTests !== passed ||
|
|
138
|
+
report.numFailedTests !== failed ||
|
|
139
|
+
report.numPendingTests !== pending ||
|
|
140
|
+
report.numTodoTests !== todo
|
|
141
|
+
) {
|
|
142
|
+
context.addIssue({
|
|
143
|
+
code: 'custom',
|
|
144
|
+
message: 'Test counters disagree with assertion results',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
report.numTotalTestSuites !==
|
|
149
|
+
report.numPassedTestSuites + report.numFailedTestSuites + report.numPendingTestSuites
|
|
150
|
+
) {
|
|
151
|
+
context.addIssue({ code: 'custom', message: 'Suite counters are inconsistent' });
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
report.testResults.some(
|
|
155
|
+
(file) =>
|
|
156
|
+
file.status === 'passed' &&
|
|
157
|
+
file.assertionResults.some((test) => test.status === 'failed'),
|
|
158
|
+
) ||
|
|
159
|
+
(report.success &&
|
|
160
|
+
(failed > 0 ||
|
|
161
|
+
report.numFailedTestSuites > 0 ||
|
|
162
|
+
report.testResults.some((file) => file.status === 'failed')))
|
|
163
|
+
) {
|
|
164
|
+
context.addIssue({ code: 'custom', message: 'Success status contradicts failed results' });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
82
168
|
/** Zod schema for {@link FileGate}. */
|
|
83
169
|
export const fileGateSchema = gateBaseSchema.extend({
|
|
84
170
|
kind: z.literal('file'),
|
|
@@ -221,26 +307,246 @@ export type GateResultDetailsInput = z.input<typeof gateResultDetailsSchema>;
|
|
|
221
307
|
|
|
222
308
|
// ─── Result schema ────────────────────────────────────────────────────────────
|
|
223
309
|
|
|
310
|
+
/** Validate captured transport observations without treating their shape as execution authority. */
|
|
311
|
+
const capturedExecutionSchema: z.ZodType<ProcessCaptureResult> = z
|
|
312
|
+
.object({
|
|
313
|
+
started: z.boolean(),
|
|
314
|
+
targetPid: z.number().int().positive().safe().nullable(),
|
|
315
|
+
exitCode: z.number().int().nonnegative().safe().nullable(),
|
|
316
|
+
signal: z.string().min(1).nullable(),
|
|
317
|
+
error: z.string().nullable(),
|
|
318
|
+
stopped: z
|
|
319
|
+
.enum([
|
|
320
|
+
'deadline',
|
|
321
|
+
'cancelled',
|
|
322
|
+
'teardown',
|
|
323
|
+
'output-limit',
|
|
324
|
+
'resource-limit',
|
|
325
|
+
'transport-error',
|
|
326
|
+
])
|
|
327
|
+
.nullable(),
|
|
328
|
+
stdout: z.string(),
|
|
329
|
+
stderr: z.string(),
|
|
330
|
+
outputTruncated: z.boolean(),
|
|
331
|
+
durationMs: z.number().finite().nonnegative(),
|
|
332
|
+
mode: z.enum(['systemd', 'pgid']),
|
|
333
|
+
unitName: z.string().min(1).optional(),
|
|
334
|
+
nativeMemory: z.enum(['unverified', 'observed-cgroup']),
|
|
335
|
+
resourceLimits: z
|
|
336
|
+
.object({
|
|
337
|
+
cgroup: z.string().startsWith('/'),
|
|
338
|
+
memoryMaxBytes: z.number().int().positive().safe().nullable(),
|
|
339
|
+
tasksMax: z.number().int().positive().safe().nullable(),
|
|
340
|
+
})
|
|
341
|
+
.strict()
|
|
342
|
+
.optional(),
|
|
343
|
+
cleanupScope: z.enum(['process-group', 'direct-child']),
|
|
344
|
+
transportClosed: z.literal(true),
|
|
345
|
+
targetCloseObserved: z.boolean(),
|
|
346
|
+
cleanupObservation: z.enum([
|
|
347
|
+
'scope-terminal',
|
|
348
|
+
'process-group-absent',
|
|
349
|
+
'process-group-signalled',
|
|
350
|
+
'unverified',
|
|
351
|
+
]),
|
|
352
|
+
cleanupErrors: z.array(z.string()),
|
|
353
|
+
})
|
|
354
|
+
.strict()
|
|
355
|
+
.superRefine((execution, context) => {
|
|
356
|
+
if (
|
|
357
|
+
execution.started !== (execution.targetPid !== null) ||
|
|
358
|
+
(!execution.started && (execution.exitCode !== null || execution.signal !== null)) ||
|
|
359
|
+
(execution.exitCode !== null && execution.signal !== null) ||
|
|
360
|
+
(!execution.targetCloseObserved && (execution.exitCode !== null || execution.signal !== null))
|
|
361
|
+
)
|
|
362
|
+
context.addIssue({ code: 'custom', message: 'Contradictory target lifecycle observation' });
|
|
363
|
+
if (execution.nativeMemory === 'observed-cgroup' && !execution.resourceLimits?.memoryMaxBytes)
|
|
364
|
+
context.addIssue({
|
|
365
|
+
code: 'custom',
|
|
366
|
+
message: 'Observed native-memory claim requires a finite kernel limit',
|
|
367
|
+
});
|
|
368
|
+
if (
|
|
369
|
+
execution.resourceLimits &&
|
|
370
|
+
(execution.mode !== 'systemd' ||
|
|
371
|
+
!execution.unitName ||
|
|
372
|
+
!execution.resourceLimits.cgroup.split('/').includes(execution.unitName) ||
|
|
373
|
+
execution.resourceLimits.cgroup.split('/').includes('..'))
|
|
374
|
+
)
|
|
375
|
+
context.addIssue({
|
|
376
|
+
code: 'custom',
|
|
377
|
+
message: 'Resource observation must name the exact owned scope',
|
|
378
|
+
});
|
|
379
|
+
if (
|
|
380
|
+
execution.cleanupObservation === 'scope-terminal' &&
|
|
381
|
+
(execution.mode !== 'systemd' || !execution.unitName)
|
|
382
|
+
)
|
|
383
|
+
context.addIssue({
|
|
384
|
+
code: 'custom',
|
|
385
|
+
message: 'Terminal scope observation requires an identified systemd scope',
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
/** Absolute captured paths; canonical ownership is checked by the runtime verifier. */
|
|
390
|
+
const gateBindingPathSchema = z
|
|
391
|
+
.string()
|
|
392
|
+
.min(1)
|
|
393
|
+
.refine(
|
|
394
|
+
(value) => /^(?:\/|[A-Za-z]:[\\/]|\\\\)/.test(value) && !value.includes('\0'),
|
|
395
|
+
'Captured gate paths must be absolute and contain no null byte',
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
/** Strict persistence shape for existing acceptance-result provenance. */
|
|
399
|
+
const acceptanceGateBindingSchema: z.ZodType<AcceptanceGateBinding> = z
|
|
400
|
+
.object({
|
|
401
|
+
version: z.literal(1),
|
|
402
|
+
verificationId: z.string().uuid(),
|
|
403
|
+
identity: z
|
|
404
|
+
.object({
|
|
405
|
+
projectId: z.string().min(1),
|
|
406
|
+
projectRoot: gateBindingPathSchema,
|
|
407
|
+
actor: z.string().min(1),
|
|
408
|
+
operation: z.literal('check.gate.verify'),
|
|
409
|
+
idempotencyKey: z.string().min(1),
|
|
410
|
+
})
|
|
411
|
+
.strict(),
|
|
412
|
+
taskId: z.string().min(1),
|
|
413
|
+
criterionId: z.string().uuid(),
|
|
414
|
+
criterionHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
415
|
+
gateHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
416
|
+
capturedAt: z.string().datetime(),
|
|
417
|
+
deadlineAt: z.number().int().positive().safe(),
|
|
418
|
+
invocation: z
|
|
419
|
+
.object({
|
|
420
|
+
command: z.string().min(1),
|
|
421
|
+
args: z.array(z.string()),
|
|
422
|
+
cwd: gateBindingPathSchema,
|
|
423
|
+
environmentHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
424
|
+
})
|
|
425
|
+
.strict()
|
|
426
|
+
.optional(),
|
|
427
|
+
artifacts: z.array(
|
|
428
|
+
z
|
|
429
|
+
.object({
|
|
430
|
+
path: gateBindingPathSchema,
|
|
431
|
+
sha256: z
|
|
432
|
+
.string()
|
|
433
|
+
.regex(/^[a-f0-9]{64}$/)
|
|
434
|
+
.nullable(),
|
|
435
|
+
bytes: z.number().int().nonnegative().safe().nullable(),
|
|
436
|
+
})
|
|
437
|
+
.strict()
|
|
438
|
+
.superRefine((artifact, context) => {
|
|
439
|
+
if ((artifact.sha256 === null) !== (artifact.bytes === null))
|
|
440
|
+
context.addIssue({
|
|
441
|
+
code: 'custom',
|
|
442
|
+
message: 'Absent input requires both null digest and size',
|
|
443
|
+
});
|
|
444
|
+
}),
|
|
445
|
+
),
|
|
446
|
+
})
|
|
447
|
+
.strict()
|
|
448
|
+
.superRefine((binding, context) => {
|
|
449
|
+
if (new Set(binding.artifacts.map(({ path }) => path)).size !== binding.artifacts.length)
|
|
450
|
+
context.addIssue({
|
|
451
|
+
code: 'custom',
|
|
452
|
+
path: ['artifacts'],
|
|
453
|
+
message: 'Input paths must be unique',
|
|
454
|
+
});
|
|
455
|
+
if (Date.parse(binding.capturedAt) >= binding.deadlineAt)
|
|
456
|
+
context.addIssue({
|
|
457
|
+
code: 'custom',
|
|
458
|
+
path: ['deadlineAt'],
|
|
459
|
+
message: 'Input capture must precede the original deadline',
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
224
463
|
/**
|
|
225
464
|
* Zod schema for {@link AcceptanceGateResult}.
|
|
226
465
|
*
|
|
227
466
|
* Validates the shape produced by the gate runner and stored in
|
|
228
467
|
* `lifecycle_gate_results`.
|
|
468
|
+
* @remarks Historical records without execution detail remain readable. New detail is
|
|
469
|
+
* preserved strictly; interrupted or contradictory observations cannot supply a verdict.
|
|
470
|
+
* Validation proves shape consistency, not freshness, task binding or authentic execution.
|
|
471
|
+
* @example
|
|
472
|
+
* ```typescript
|
|
473
|
+
* const result = acceptanceGateResultSchema.parse(storedResult);
|
|
474
|
+
* ```
|
|
229
475
|
*/
|
|
230
|
-
export const acceptanceGateResultSchema = z
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
476
|
+
export const acceptanceGateResultSchema = z
|
|
477
|
+
.object({
|
|
478
|
+
binding: acceptanceGateBindingSchema.optional(),
|
|
479
|
+
index: z.number().int().nonnegative(),
|
|
480
|
+
req: z.string().optional(),
|
|
481
|
+
kind: z.enum(['test', 'file', 'command', 'lint', 'http', 'manual']),
|
|
482
|
+
result: z.enum(['pass', 'fail', 'warn', 'skipped', 'error']),
|
|
483
|
+
durationMs: z.number().nonnegative(),
|
|
484
|
+
/** Typed kind-specific detail payload (T802). */
|
|
485
|
+
details: gateResultDetailsSchema.optional(),
|
|
486
|
+
/** Actual target/transport/containment observations when supplied by the runner. */
|
|
487
|
+
execution: capturedExecutionSchema.optional(),
|
|
488
|
+
evidence: z.string().optional(),
|
|
489
|
+
errorMessage: z.string().optional(),
|
|
490
|
+
/** ISO 8601 timestamp. */
|
|
491
|
+
checkedAt: z.string().datetime(),
|
|
492
|
+
checkedBy: z.string().min(1),
|
|
493
|
+
})
|
|
494
|
+
.superRefine((result, context) => {
|
|
495
|
+
const binding = result.binding;
|
|
496
|
+
if (binding) {
|
|
497
|
+
if (binding.identity.actor !== result.checkedBy)
|
|
498
|
+
context.addIssue({
|
|
499
|
+
code: 'custom',
|
|
500
|
+
path: ['binding', 'identity', 'actor'],
|
|
501
|
+
message: 'Result actor must match captured operation identity',
|
|
502
|
+
});
|
|
503
|
+
if (Date.parse(binding.capturedAt) > Date.parse(result.checkedAt))
|
|
504
|
+
context.addIssue({
|
|
505
|
+
code: 'custom',
|
|
506
|
+
path: ['binding', 'capturedAt'],
|
|
507
|
+
message: 'Result cannot precede input capture',
|
|
508
|
+
});
|
|
509
|
+
const executable = ['test', 'command', 'lint'].includes(result.kind);
|
|
510
|
+
if (executable && !binding.invocation)
|
|
511
|
+
context.addIssue({
|
|
512
|
+
code: 'custom',
|
|
513
|
+
path: ['binding', 'invocation'],
|
|
514
|
+
message: 'Executable gate requires captured invocation',
|
|
515
|
+
});
|
|
516
|
+
if (['pass', 'fail', 'warn'].includes(result.result)) {
|
|
517
|
+
if (executable && !result.execution)
|
|
518
|
+
context.addIssue({
|
|
519
|
+
code: 'custom',
|
|
520
|
+
path: ['execution'],
|
|
521
|
+
message: 'Bound executable verdict requires process observation',
|
|
522
|
+
});
|
|
523
|
+
if (Date.parse(result.checkedAt) > binding.deadlineAt)
|
|
524
|
+
context.addIssue({
|
|
525
|
+
code: 'custom',
|
|
526
|
+
path: ['checkedAt'],
|
|
527
|
+
message: 'Verdict exceeds the original admitted deadline',
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const execution = result.execution;
|
|
532
|
+
if (
|
|
533
|
+
execution &&
|
|
534
|
+
['pass', 'fail', 'warn'].includes(result.result) &&
|
|
535
|
+
(!execution.started ||
|
|
536
|
+
!execution.targetCloseObserved ||
|
|
537
|
+
execution.exitCode === null ||
|
|
538
|
+
execution.signal !== null ||
|
|
539
|
+
execution.error !== null ||
|
|
540
|
+
execution.stopped !== null ||
|
|
541
|
+
execution.outputTruncated ||
|
|
542
|
+
execution.cleanupErrors.length > 0)
|
|
543
|
+
)
|
|
544
|
+
context.addIssue({
|
|
545
|
+
code: 'custom',
|
|
546
|
+
path: ['execution'],
|
|
547
|
+
message: 'Incomplete process observation cannot establish a gate verdict',
|
|
548
|
+
});
|
|
549
|
+
});
|
|
244
550
|
|
|
245
551
|
/** Inferred TypeScript type from {@link acceptanceGateResultSchema}. */
|
|
246
552
|
export type AcceptanceGateResultInput = z.input<typeof acceptanceGateResultSchema>;
|
package/src/acceptance-gate.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Acceptance gate discriminated union and result types.
|
|
3
3
|
*
|
|
4
4
|
* A machine-verifiable acceptance gate. Gates coexist with free-text criteria
|
|
5
|
-
* in `Task.acceptance
|
|
6
|
-
*
|
|
5
|
+
* in `Task.acceptance`. The runner returns observed results; persistence and
|
|
6
|
+
* completion binding require the caller's explicit verification workflow.
|
|
7
7
|
*
|
|
8
8
|
* Six gate kinds are supported:
|
|
9
|
-
* - `test` — run a command and assert exit code
|
|
9
|
+
* - `test` — run a command and assert exit code; unmeasured test-count requirements remain errors
|
|
10
10
|
* - `file` — assert properties of a file on disk
|
|
11
11
|
* - `command` — run any CLI command and assert exit code / stdout
|
|
12
12
|
* - `lint` — run a static-analysis tool and require a clean result
|
|
@@ -19,6 +19,38 @@
|
|
|
19
19
|
* @see {@link https://github.com/kryptobaseddev/cleo} T760 RCASD hardening
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import type { OperationExecutionContext, OperationExecutionIdentity } from './jobs.js';
|
|
23
|
+
import type {
|
|
24
|
+
ProcessCaptureOptions,
|
|
25
|
+
ProcessCaptureResult,
|
|
26
|
+
SystemdControlContext,
|
|
27
|
+
} from './resource-governor.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Captured options for evaluating acceptance gates without renewing operation authority.
|
|
31
|
+
* @remarks An absent execution context receives a two-second shared foreground budget.
|
|
32
|
+
* Per-gate timeouts can tighten it; long work requires explicit runtime admission.
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const options: AcceptanceGateRunOptions = { projectRoot: '/project', execution };
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface AcceptanceGateRunOptions
|
|
39
|
+
extends Pick<ProcessCaptureOptions, 'memoryMaxMb' | 'tasksMax'> {
|
|
40
|
+
/** Explicit project root, or the captured operation's project root. */
|
|
41
|
+
projectRoot?: string;
|
|
42
|
+
/** Manual gates remain skipped and never become machine proof. */
|
|
43
|
+
skipManual?: boolean;
|
|
44
|
+
/** Original admitted operation, including project identity, deadline and cancellation. */
|
|
45
|
+
execution?: OperationExecutionContext;
|
|
46
|
+
/** Explicit captured environment; defaults to a copy of the calling environment. */
|
|
47
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
48
|
+
/** Aggregate process output and file-content read bound per gate. */
|
|
49
|
+
maxOutputBytes?: number;
|
|
50
|
+
/** Existing manager context, separate from the child environment. */
|
|
51
|
+
systemdControl?: SystemdControlContext;
|
|
52
|
+
}
|
|
53
|
+
|
|
22
54
|
// ─── Base ────────────────────────────────────────────────────────────────────
|
|
23
55
|
|
|
24
56
|
/** Fields every gate variant carries. */
|
|
@@ -48,7 +80,7 @@ export interface GateBase {
|
|
|
48
80
|
/**
|
|
49
81
|
* Gate timeout in milliseconds.
|
|
50
82
|
*
|
|
51
|
-
* @defaultValue
|
|
83
|
+
* @defaultValue 60_000 (further bounded by the shared operation deadline)
|
|
52
84
|
*/
|
|
53
85
|
timeoutMs?: number;
|
|
54
86
|
}
|
|
@@ -56,8 +88,8 @@ export interface GateBase {
|
|
|
56
88
|
// ─── Variants ────────────────────────────────────────────────────────────────
|
|
57
89
|
|
|
58
90
|
/**
|
|
59
|
-
* Run a command
|
|
60
|
-
*
|
|
91
|
+
* Run a command and verify its actual exit status. Positive `minCount` requires
|
|
92
|
+
* a structured test-count capability and currently returns an explicit error. Designed for test suites:
|
|
61
93
|
* `{ kind: 'test', command: 'pnpm test', expect: 'pass' }`.
|
|
62
94
|
*/
|
|
63
95
|
export interface TestGate extends GateBase {
|
|
@@ -71,7 +103,7 @@ export interface TestGate extends GateBase {
|
|
|
71
103
|
* - `"exit0"`: exit code 0 only (permissive mode).
|
|
72
104
|
*/
|
|
73
105
|
expect: 'pass' | 'exit0';
|
|
74
|
-
/** Minimum
|
|
106
|
+
/** Minimum test count; unsupported count evidence must return error, never infer a count from exit zero. */
|
|
75
107
|
minCount?: number;
|
|
76
108
|
/** Working directory relative to project root. Default `.`. */
|
|
77
109
|
cwd?: string;
|
|
@@ -181,8 +213,8 @@ export interface LintGate extends GateBase {
|
|
|
181
213
|
|
|
182
214
|
/**
|
|
183
215
|
* Hit a URL and assert HTTP status and optional body match. For tasks that
|
|
184
|
-
* ship a webapp or API.
|
|
185
|
-
*
|
|
216
|
+
* ship a webapp or API. Probing an existing service is supported; `startCommand`
|
|
217
|
+
* currently returns an explicit error pending an admitted owned service lifetime.
|
|
186
218
|
*/
|
|
187
219
|
export interface HttpGate extends GateBase {
|
|
188
220
|
kind: 'http';
|
|
@@ -319,16 +351,92 @@ export type GateResultDetails =
|
|
|
319
351
|
|
|
320
352
|
// ─── Result ──────────────────────────────────────────────────────────────────
|
|
321
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Exact bytes observed for one declared task input or resolved verification harness.
|
|
356
|
+
* @remarks A missing input is explicit, with both digest and size null. This inventory
|
|
357
|
+
* does not claim coverage of undeclared runtime dependencies or external services.
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* const input: AcceptanceGateArtifact = { path: '/project/check.mjs', sha256: 'a'.repeat(64), bytes: 42 };
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
export interface AcceptanceGateArtifact {
|
|
364
|
+
/** Captured absolute input path, resolved within the admitted project scope. */
|
|
365
|
+
path: string;
|
|
366
|
+
/** SHA-256 of actual input bytes; null only for an observed absent path. */
|
|
367
|
+
sha256: string | null;
|
|
368
|
+
/** Exact byte length, or null alongside an absent digest. */
|
|
369
|
+
bytes: number | null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Executable invocation captured before launching a typed verification gate.
|
|
374
|
+
* @remarks Environment values are represented only by a digest; credentials must
|
|
375
|
+
* not be duplicated into receipts. The verifier compares actual launch inputs.
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* const invocation: AcceptanceGateInvocation = { command: 'node', args: ['check.mjs'], cwd: '/project', environmentHash: 'b'.repeat(64) };
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
export interface AcceptanceGateInvocation {
|
|
382
|
+
/** Exact executable passed to the existing process port. */
|
|
383
|
+
command: string;
|
|
384
|
+
/** Ordered arguments, without shell re-interpretation. */
|
|
385
|
+
args: string[];
|
|
386
|
+
/** Captured absolute process working directory. */
|
|
387
|
+
cwd: string;
|
|
388
|
+
/** SHA-256 of the canonically ordered effective environment. */
|
|
389
|
+
environmentHash: string;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Task and input binding attached to an explicitly verified acceptance result.
|
|
394
|
+
* @remarks This is provenance, not authority by itself. Persistence and completion
|
|
395
|
+
* must compare current canonical task/AC rows, input bytes and captured execution.
|
|
396
|
+
* No gate runs implicitly on task creation or editing. Historical unbound results
|
|
397
|
+
* remain readable but do not establish typed requirement completion.
|
|
398
|
+
* @example
|
|
399
|
+
* ```typescript
|
|
400
|
+
* const owner = result.binding?.identity.projectId;
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
export interface AcceptanceGateBinding {
|
|
404
|
+
/** Version of the captured binding contract. */
|
|
405
|
+
version: 1;
|
|
406
|
+
/** Unique verification attempt whose result and receipt commit atomically. */
|
|
407
|
+
verificationId: string;
|
|
408
|
+
/** Existing captured operation identity, including actor and retry identity. */
|
|
409
|
+
identity: OperationExecutionIdentity;
|
|
410
|
+
/** Exact canonical task owning this criterion. */
|
|
411
|
+
taskId: string;
|
|
412
|
+
/** Current normalized acceptance row identifier. */
|
|
413
|
+
criterionId: string;
|
|
414
|
+
/** SHA-256 over the criterion's canonical persisted payload. */
|
|
415
|
+
criterionHash: string;
|
|
416
|
+
/** SHA-256 over the complete canonical gate, including REQ and launch options. */
|
|
417
|
+
gateHash: string;
|
|
418
|
+
/** ISO time of input capture before execution. */
|
|
419
|
+
capturedAt: string;
|
|
420
|
+
/** Original admitted absolute deadline in epoch milliseconds; never renewed. */
|
|
421
|
+
deadlineAt: number;
|
|
422
|
+
/** Actual process inputs; required for executable gate kinds. */
|
|
423
|
+
invocation?: AcceptanceGateInvocation;
|
|
424
|
+
/** Explicit bounded input inventory, including untracked harness/task files. */
|
|
425
|
+
artifacts: AcceptanceGateArtifact[];
|
|
426
|
+
}
|
|
427
|
+
|
|
322
428
|
/**
|
|
323
429
|
* Result of running one acceptance gate.
|
|
324
430
|
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
431
|
+
* Returned by the runner. Canonical verification explicitly persists bound results;
|
|
432
|
+
* the result shape alone does not prove persistence or authorize completion.
|
|
327
433
|
*
|
|
328
434
|
* NOTE: Named `AcceptanceGateResult` (not `GateResult`) for historical
|
|
329
435
|
* clarity — it describes acceptance-criterion gate outcomes specifically.
|
|
330
436
|
*/
|
|
331
437
|
export interface AcceptanceGateResult {
|
|
438
|
+
/** Explicit verification binding; absent on historical/unpersisted runner observations. */
|
|
439
|
+
binding?: AcceptanceGateBinding;
|
|
332
440
|
/** Zero-based index in the task's acceptance array. */
|
|
333
441
|
index: number;
|
|
334
442
|
/** REQ-ID if the gate had one, else `undefined`. */
|
|
@@ -356,6 +464,8 @@ export interface AcceptanceGateResult {
|
|
|
356
464
|
result: 'pass' | 'fail' | 'warn' | 'skipped' | 'error';
|
|
357
465
|
/** Wall-clock duration of the gate execution in milliseconds. */
|
|
358
466
|
durationMs: number;
|
|
467
|
+
/** Actual process outcome and containment evidence; wrapper status is not target proof. */
|
|
468
|
+
execution?: ProcessCaptureResult;
|
|
359
469
|
/**
|
|
360
470
|
* Typed kind-specific detail payload.
|
|
361
471
|
*
|
|
@@ -384,3 +494,15 @@ export interface AcceptanceGateResult {
|
|
|
384
494
|
/** Agent identifier or `"human"` that ran or attested the gate. */
|
|
385
495
|
checkedBy: string;
|
|
386
496
|
}
|
|
497
|
+
|
|
498
|
+
/** Canonical typed-verification audit details stored with results in the task transaction. */
|
|
499
|
+
export interface AcceptanceGateVerificationReceipt {
|
|
500
|
+
/** One admitted verification batch identity shared by every result. */
|
|
501
|
+
verificationId: string;
|
|
502
|
+
/** SHA-256 of the exact persisted result-array JSON bytes. */
|
|
503
|
+
resultHash: string;
|
|
504
|
+
/** Explicit verification operation; never inferred from generic completion evidence. */
|
|
505
|
+
operation: 'check.gate.verify';
|
|
506
|
+
/** Overall generic-plus-typed verification outcome at this observation. */
|
|
507
|
+
passed: boolean;
|
|
508
|
+
}
|
package/src/audit.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* @epic T1216
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import type { ProcessCaptureResult, ProcessLaunchExecution } from './resource-governor.js';
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* A single commit entry extracted from git log.
|
|
18
20
|
*/
|
|
@@ -51,17 +53,22 @@ export interface ReleaseTagEntry {
|
|
|
51
53
|
* necessarily from the task DB.
|
|
52
54
|
*/
|
|
53
55
|
export interface ReconstructResult {
|
|
56
|
+
/** Assessment provenance; absent only on legacy results. New assessments always emit it.
|
|
57
|
+
* @defaultValue undefined on legacy records only
|
|
58
|
+
*/
|
|
59
|
+
assessment?: ReconstructAssessment;
|
|
60
|
+
|
|
54
61
|
/** The task ID being reconstructed (e.g. `T991`). */
|
|
55
62
|
taskId: string;
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
65
|
* Commits whose message directly references `taskId`.
|
|
59
|
-
*
|
|
66
|
+
* Exact word-boundary task tokens in the full subject and body, never numeric prefixes.
|
|
60
67
|
*/
|
|
61
68
|
directCommits: CommitEntry[];
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
|
-
* Numeric child ID range inferred from commit-message mining and
|
|
71
|
+
* Numeric child ID range inferred from commit-message mining and numeric proximity (not authoritative containment).
|
|
65
72
|
* Both bounds are inclusive. `null` when no children could be inferred.
|
|
66
73
|
*/
|
|
67
74
|
childIdRange: { min: string; max: string } | null;
|
|
@@ -102,3 +109,66 @@ export interface ReconstructResult {
|
|
|
102
109
|
*/
|
|
103
110
|
inferredChildren: string[];
|
|
104
111
|
}
|
|
112
|
+
|
|
113
|
+
/** Options for bounded Git-backed lineage assessment.
|
|
114
|
+
* @remarks These options may shorten but never extend an inherited operation lifetime.
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const options: ReconstructOptions = { execution: { deadlineAt: Date.now() + 1000 } };
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export interface ReconstructOptions {
|
|
121
|
+
/** Original caller deadline and cancellation.
|
|
122
|
+
* @defaultValue inherited context, otherwise one two-second foreground budget
|
|
123
|
+
*/
|
|
124
|
+
execution?: ProcessLaunchExecution;
|
|
125
|
+
/** Combined capture byte ceiling across all commands.
|
|
126
|
+
* @defaultValue 8388608
|
|
127
|
+
*/
|
|
128
|
+
maxOutputBytes?: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Observed bounded command outcome without duplicating captured Git history.
|
|
132
|
+
* @remarks Target outcome and cleanup evidence remain distinct.
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const stopped = command.stopped;
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export interface ReconstructCommand extends Omit<ProcessCaptureResult, 'stdout' | 'stderr'> {
|
|
139
|
+
/** Exact requested Git arguments. */
|
|
140
|
+
args: readonly string[];
|
|
141
|
+
/** Diagnostic stderr, bounded by the shared output limit. */
|
|
142
|
+
stderr: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Coverage and failure truth for a lineage assessment.
|
|
146
|
+
* @remarks Current means the observed local Git scope was assessed, not complete remote history
|
|
147
|
+
* or proven task containment. Partial results must not authorize provenance repair.
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* if (result.assessment?.coverage !== 'current') return;
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export interface ReconstructAssessment {
|
|
154
|
+
/** Absolute repository root captured before asynchronous work. */
|
|
155
|
+
repositoryRoot: string;
|
|
156
|
+
/** One absolute execution deadline, shared with every command and computation stage. */
|
|
157
|
+
deadlineAt: number;
|
|
158
|
+
/** Completeness of the observed local Git history and release-tag assessment. */
|
|
159
|
+
coverage: 'current' | 'partial' | 'failed';
|
|
160
|
+
/** Shallow history is explicitly incomplete; null means the probe did not complete. */
|
|
161
|
+
shallow: boolean | null;
|
|
162
|
+
/** Number of commits parsed from the bounded history, not total remote commits. */
|
|
163
|
+
observedCommits: number;
|
|
164
|
+
/** Whether all history records were parsed successfully. */
|
|
165
|
+
historyComplete: boolean;
|
|
166
|
+
/** Whether every commit-targeting tag was assessed successfully. */
|
|
167
|
+
tagsComplete: boolean;
|
|
168
|
+
/** Captured process verdicts and cleanup observations. */
|
|
169
|
+
commands: ReconstructCommand[];
|
|
170
|
+
/** Explicit failures and incomplete-scope reasons; never replaced with healthy empty arrays. */
|
|
171
|
+
diagnostics: Array<{ code: string; stage: string; message: string }>;
|
|
172
|
+
/** Non-authoritative inference and local-scope limitations retained even for current coverage. */
|
|
173
|
+
limitations: string[];
|
|
174
|
+
}
|