@cleocode/contracts 2026.9.7 → 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.
Files changed (151) hide show
  1. package/dist/__tests__/acceptance-gate.test.js +242 -1
  2. package/dist/__tests__/acceptance-gate.test.js.map +1 -1
  3. package/dist/__tests__/evidence-atom-schema.test.js +17 -14
  4. package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
  5. package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
  6. package/dist/__tests__/operations-registry.test.js +18 -0
  7. package/dist/__tests__/operations-registry.test.js.map +1 -1
  8. package/dist/acceptance-gate-schema.d.ts +60 -2
  9. package/dist/acceptance-gate-schema.d.ts.map +1 -1
  10. package/dist/acceptance-gate-schema.js +273 -1
  11. package/dist/acceptance-gate-schema.js.map +1 -1
  12. package/dist/acceptance-gate.d.ts +122 -11
  13. package/dist/acceptance-gate.d.ts.map +1 -1
  14. package/dist/acceptance-gate.js +3 -3
  15. package/dist/audit.d.ts +71 -2
  16. package/dist/audit.d.ts.map +1 -1
  17. package/dist/backup-manifest.d.ts +80 -0
  18. package/dist/backup-manifest.d.ts.map +1 -1
  19. package/dist/caamp-markers.d.ts +20 -0
  20. package/dist/caamp-markers.d.ts.map +1 -1
  21. package/dist/capabilities.d.ts +282 -0
  22. package/dist/capabilities.d.ts.map +1 -1
  23. package/dist/changesets.d.ts +4 -4
  24. package/dist/code-symbol.d.ts +33 -0
  25. package/dist/code-symbol.d.ts.map +1 -1
  26. package/dist/daemon/health.d.ts +3 -3
  27. package/dist/data-accessor.d.ts +15 -0
  28. package/dist/data-accessor.d.ts.map +1 -1
  29. package/dist/data-accessor.js.map +1 -1
  30. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +19 -1
  31. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -1
  32. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  33. package/dist/dispatch/operations-registry.js +684 -3
  34. package/dist/dispatch/operations-registry.js.map +1 -1
  35. package/dist/docs-taxonomy.d.ts +25 -0
  36. package/dist/docs-taxonomy.d.ts.map +1 -1
  37. package/dist/docs-taxonomy.js.map +1 -1
  38. package/dist/evidence-atom-schema.d.ts +7 -6
  39. package/dist/evidence-atom-schema.d.ts.map +1 -1
  40. package/dist/evidence-atom-schema.js +10 -9
  41. package/dist/evidence-atom-schema.js.map +1 -1
  42. package/dist/graph.d.ts +283 -2
  43. package/dist/graph.d.ts.map +1 -1
  44. package/dist/index.d.ts +6 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/invariants/adr-070-orchestration.d.ts.map +1 -1
  48. package/dist/invariants/adr-070-orchestration.js +1 -1
  49. package/dist/invariants/adr-070-orchestration.js.map +1 -1
  50. package/dist/invariants/adr-073-saga.js +2 -2
  51. package/dist/invariants/adr-073-saga.js.map +1 -1
  52. package/dist/jobs.d.ts +329 -3
  53. package/dist/jobs.d.ts.map +1 -1
  54. package/dist/knowledge-health.d.ts +695 -0
  55. package/dist/knowledge-health.d.ts.map +1 -0
  56. package/dist/knowledge-health.js +8 -0
  57. package/dist/knowledge-health.js.map +1 -0
  58. package/dist/llm/catalog-schema.d.ts +4 -4
  59. package/dist/memory/observe.d.ts +4 -2
  60. package/dist/memory/observe.d.ts.map +1 -1
  61. package/dist/memory/search.d.ts +4 -0
  62. package/dist/memory/search.d.ts.map +1 -1
  63. package/dist/nexus-living-brain-ops.d.ts +28 -3
  64. package/dist/nexus-living-brain-ops.d.ts.map +1 -1
  65. package/dist/nexus-tasks-bridge-ops.d.ts +11 -0
  66. package/dist/nexus-tasks-bridge-ops.d.ts.map +1 -1
  67. package/dist/operations/admin.d.ts +10 -4
  68. package/dist/operations/admin.d.ts.map +1 -1
  69. package/dist/operations/docs.d.ts +138 -0
  70. package/dist/operations/docs.d.ts.map +1 -1
  71. package/dist/operations/docs.js +47 -0
  72. package/dist/operations/docs.js.map +1 -1
  73. package/dist/operations/focus.d.ts +7 -0
  74. package/dist/operations/focus.d.ts.map +1 -1
  75. package/dist/operations/nexus.d.ts +7 -2
  76. package/dist/operations/nexus.d.ts.map +1 -1
  77. package/dist/operations/output-contracts-data.d.ts.map +1 -1
  78. package/dist/operations/output-contracts-data.js +31 -3
  79. package/dist/operations/output-contracts-data.js.map +1 -1
  80. package/dist/operations/research.d.ts +33 -0
  81. package/dist/operations/research.d.ts.map +1 -1
  82. package/dist/operations/tasks.d.ts +107 -4
  83. package/dist/operations/tasks.d.ts.map +1 -1
  84. package/dist/operations/tasks.js +5 -0
  85. package/dist/operations/tasks.js.map +1 -1
  86. package/dist/package-artifact.d.ts +112 -0
  87. package/dist/package-artifact.d.ts.map +1 -0
  88. package/dist/package-artifact.js +8 -0
  89. package/dist/package-artifact.js.map +1 -0
  90. package/dist/release/evidence-atoms.d.ts +10 -0
  91. package/dist/release/evidence-atoms.d.ts.map +1 -1
  92. package/dist/release/evidence-atoms.js +6 -0
  93. package/dist/release/evidence-atoms.js.map +1 -1
  94. package/dist/release/plan.d.ts +6 -6
  95. package/dist/resource-governor.d.ts +114 -0
  96. package/dist/resource-governor.d.ts.map +1 -1
  97. package/dist/resource-governor.js.map +1 -1
  98. package/dist/supervisor-ipc/messages.d.ts +6 -6
  99. package/dist/task-record.d.ts +25 -4
  100. package/dist/task-record.d.ts.map +1 -1
  101. package/dist/task.d.ts +55 -36
  102. package/dist/task.d.ts.map +1 -1
  103. package/dist/task.js.map +1 -1
  104. package/dist/tasks/archive.d.ts +3 -3
  105. package/dist/templates/manifest.d.ts +1 -1
  106. package/dist/workgraph.d.ts +5 -3
  107. package/dist/workgraph.d.ts.map +1 -1
  108. package/dist/workgraph.js +19 -0
  109. package/dist/workgraph.js.map +1 -1
  110. package/package.json +2 -2
  111. package/schemas/gate-result.schema.json +350 -1
  112. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  113. package/src/__tests__/acceptance-gate.test.ts +282 -1
  114. package/src/__tests__/evidence-atom-schema.test.ts +17 -14
  115. package/src/__tests__/memory-wire-shapes.test.ts +2 -0
  116. package/src/__tests__/operations-registry.test.ts +26 -0
  117. package/src/acceptance-gate-schema.ts +320 -14
  118. package/src/acceptance-gate.ts +133 -11
  119. package/src/audit.ts +72 -2
  120. package/src/backup-manifest.ts +88 -0
  121. package/src/caamp-markers.ts +22 -0
  122. package/src/capabilities.ts +311 -0
  123. package/src/code-symbol.ts +37 -0
  124. package/src/data-accessor.ts +16 -0
  125. package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +31 -1
  126. package/src/dispatch/operations-registry.ts +689 -3
  127. package/src/docs-taxonomy.ts +26 -0
  128. package/src/evidence-atom-schema.ts +10 -9
  129. package/src/graph.ts +326 -2
  130. package/src/index.ts +43 -1
  131. package/src/invariants/adr-070-orchestration.ts +2 -1
  132. package/src/invariants/adr-073-saga.ts +2 -2
  133. package/src/jobs.ts +382 -3
  134. package/src/knowledge-health.ts +748 -0
  135. package/src/memory/observe.ts +4 -2
  136. package/src/memory/search.ts +4 -0
  137. package/src/nexus-living-brain-ops.ts +34 -3
  138. package/src/nexus-tasks-bridge-ops.ts +12 -0
  139. package/src/operations/admin.ts +11 -4
  140. package/src/operations/docs.ts +153 -0
  141. package/src/operations/focus.ts +11 -0
  142. package/src/operations/nexus.ts +7 -1
  143. package/src/operations/output-contracts-data.ts +33 -3
  144. package/src/operations/research.ts +36 -0
  145. package/src/operations/tasks.ts +116 -4
  146. package/src/package-artifact.ts +127 -0
  147. package/src/release/evidence-atoms.ts +6 -0
  148. package/src/resource-governor.ts +132 -0
  149. package/src/task-record.ts +27 -4
  150. package/src/task.ts +58 -36
  151. 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.object({
231
- index: z.number().int().nonnegative(),
232
- req: z.string().optional(),
233
- kind: z.enum(['test', 'file', 'command', 'lint', 'http', 'manual']),
234
- result: z.enum(['pass', 'fail', 'warn', 'skipped', 'error']),
235
- durationMs: z.number().nonnegative(),
236
- /** Typed kind-specific detail payload (T802). */
237
- details: gateResultDetailsSchema.optional(),
238
- evidence: z.string().optional(),
239
- errorMessage: z.string().optional(),
240
- /** ISO 8601 timestamp. */
241
- checkedAt: z.string().datetime(),
242
- checkedBy: z.string().min(1),
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>;
@@ -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`; the runtime executes only gates and records results in
6
- * `task.verification.gateResults` and in the `lifecycle_gate_results` DB table.
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 / test-count
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 120_000
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; pass when exit code is 0 and when at least `minCount`
60
- * tests have run. Designed for test suites:
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 number of tests that must have run. */
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. The runner starts a server only if `startCommand`
185
- * is set and tears it down after the probe.
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
- * Persisted to `lifecycle_gate_results` and summarised in
326
- * `task.verification.gateResults`.
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
- * Pattern: `<taskId>:`, `(<taskId>):`, or `<taskId> ` (space-delimited).
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 DB lookup.
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
+ }