@cleocode/contracts 2026.9.8 → 2026.9.10

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 (158) 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 +133 -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 +713 -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 +8 -7
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/invariants/adr-070-orchestration.d.ts.map +1 -1
  49. package/dist/invariants/adr-070-orchestration.js +1 -1
  50. package/dist/invariants/adr-070-orchestration.js.map +1 -1
  51. package/dist/invariants/adr-073-saga.js +2 -2
  52. package/dist/invariants/adr-073-saga.js.map +1 -1
  53. package/dist/jobs.d.ts +329 -3
  54. package/dist/jobs.d.ts.map +1 -1
  55. package/dist/knowledge-health.d.ts +695 -0
  56. package/dist/knowledge-health.d.ts.map +1 -0
  57. package/dist/knowledge-health.js +8 -0
  58. package/dist/knowledge-health.js.map +1 -0
  59. package/dist/llm/catalog-schema.d.ts +4 -4
  60. package/dist/memory/observe.d.ts +4 -2
  61. package/dist/memory/observe.d.ts.map +1 -1
  62. package/dist/memory/search.d.ts +4 -0
  63. package/dist/memory/search.d.ts.map +1 -1
  64. package/dist/nexus-living-brain-ops.d.ts +28 -3
  65. package/dist/nexus-living-brain-ops.d.ts.map +1 -1
  66. package/dist/nexus-tasks-bridge-ops.d.ts +11 -0
  67. package/dist/nexus-tasks-bridge-ops.d.ts.map +1 -1
  68. package/dist/operations/admin.d.ts +10 -4
  69. package/dist/operations/admin.d.ts.map +1 -1
  70. package/dist/operations/docs.d.ts +138 -0
  71. package/dist/operations/docs.d.ts.map +1 -1
  72. package/dist/operations/docs.js +47 -0
  73. package/dist/operations/docs.js.map +1 -1
  74. package/dist/operations/focus.d.ts +7 -0
  75. package/dist/operations/focus.d.ts.map +1 -1
  76. package/dist/operations/nexus.d.ts +7 -2
  77. package/dist/operations/nexus.d.ts.map +1 -1
  78. package/dist/operations/output-contracts-data.d.ts.map +1 -1
  79. package/dist/operations/output-contracts-data.js +31 -3
  80. package/dist/operations/output-contracts-data.js.map +1 -1
  81. package/dist/operations/research.d.ts +33 -0
  82. package/dist/operations/research.d.ts.map +1 -1
  83. package/dist/operations/tasks.d.ts +107 -4
  84. package/dist/operations/tasks.d.ts.map +1 -1
  85. package/dist/operations/tasks.js +5 -0
  86. package/dist/operations/tasks.js.map +1 -1
  87. package/dist/operations/validate.d.ts +23 -0
  88. package/dist/operations/validate.d.ts.map +1 -1
  89. package/dist/package-artifact.d.ts +112 -0
  90. package/dist/package-artifact.d.ts.map +1 -0
  91. package/dist/package-artifact.js +8 -0
  92. package/dist/package-artifact.js.map +1 -0
  93. package/dist/project-context.d.ts +18 -0
  94. package/dist/project-context.d.ts.map +1 -1
  95. package/dist/release/evidence-atoms.d.ts +10 -0
  96. package/dist/release/evidence-atoms.d.ts.map +1 -1
  97. package/dist/release/evidence-atoms.js +6 -0
  98. package/dist/release/evidence-atoms.js.map +1 -1
  99. package/dist/release/plan.d.ts +6 -6
  100. package/dist/resource-governor.d.ts +114 -0
  101. package/dist/resource-governor.d.ts.map +1 -1
  102. package/dist/resource-governor.js.map +1 -1
  103. package/dist/supervisor-ipc/messages.d.ts +6 -6
  104. package/dist/task-record.d.ts +25 -4
  105. package/dist/task-record.d.ts.map +1 -1
  106. package/dist/task.d.ts +55 -36
  107. package/dist/task.d.ts.map +1 -1
  108. package/dist/task.js.map +1 -1
  109. package/dist/tasks/archive.d.ts +3 -3
  110. package/dist/templates/manifest.d.ts +1 -1
  111. package/dist/workgraph.d.ts +5 -3
  112. package/dist/workgraph.d.ts.map +1 -1
  113. package/dist/workgraph.js +19 -0
  114. package/dist/workgraph.js.map +1 -1
  115. package/package.json +2 -2
  116. package/schemas/gate-result.schema.json +350 -1
  117. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  118. package/src/__tests__/acceptance-gate.test.ts +282 -1
  119. package/src/__tests__/evidence-atom-schema.test.ts +17 -14
  120. package/src/__tests__/memory-wire-shapes.test.ts +2 -0
  121. package/src/__tests__/operations-registry.test.ts +26 -0
  122. package/src/acceptance-gate-schema.ts +320 -14
  123. package/src/acceptance-gate.ts +144 -11
  124. package/src/audit.ts +72 -2
  125. package/src/backup-manifest.ts +88 -0
  126. package/src/caamp-markers.ts +22 -0
  127. package/src/capabilities.ts +311 -0
  128. package/src/code-symbol.ts +37 -0
  129. package/src/data-accessor.ts +16 -0
  130. package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +31 -1
  131. package/src/dispatch/operations-registry.ts +719 -3
  132. package/src/docs-taxonomy.ts +26 -0
  133. package/src/evidence-atom-schema.ts +10 -9
  134. package/src/graph.ts +326 -2
  135. package/src/index.ts +45 -1
  136. package/src/invariants/adr-070-orchestration.ts +2 -1
  137. package/src/invariants/adr-073-saga.ts +2 -2
  138. package/src/jobs.ts +382 -3
  139. package/src/knowledge-health.ts +748 -0
  140. package/src/memory/observe.ts +4 -2
  141. package/src/memory/search.ts +4 -0
  142. package/src/nexus-living-brain-ops.ts +34 -3
  143. package/src/nexus-tasks-bridge-ops.ts +12 -0
  144. package/src/operations/admin.ts +11 -4
  145. package/src/operations/docs.ts +153 -0
  146. package/src/operations/focus.ts +11 -0
  147. package/src/operations/nexus.ts +7 -1
  148. package/src/operations/output-contracts-data.ts +33 -3
  149. package/src/operations/research.ts +36 -0
  150. package/src/operations/tasks.ts +116 -4
  151. package/src/operations/validate.ts +24 -0
  152. package/src/package-artifact.ts +127 -0
  153. package/src/project-context.ts +19 -0
  154. package/src/release/evidence-atoms.ts +6 -0
  155. package/src/resource-governor.ts +132 -0
  156. package/src/task-record.ts +27 -4
  157. package/src/task.ts +58 -36
  158. 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,9 +88,14 @@ 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. Designed for test suites:
61
92
  * `{ kind: 'test', command: 'pnpm test', expect: 'pass' }`.
93
+ *
94
+ * A positive `minCount` additionally requires the gate's OWN command to emit a
95
+ * machine-readable report satisfying `testCountReportSchema` (e.g. by adding
96
+ * `--reporter=json`). A run that emits none is an error, never a pass: an exit
97
+ * code carries no count, and a report file written by some other invocation is
98
+ * not bound to this execution.
62
99
  */
63
100
  export interface TestGate extends GateBase {
64
101
  kind: 'test';
@@ -71,7 +108,13 @@ export interface TestGate extends GateBase {
71
108
  * - `"exit0"`: exit code 0 only (permissive mode).
72
109
  */
73
110
  expect: 'pass' | 'exit0';
74
- /** Minimum number of tests that must have run. */
111
+ /**
112
+ * Minimum number of PASSING tests the run must report.
113
+ *
114
+ * Satisfied only by a structured report emitted by this gate's own command;
115
+ * a run that produces none returns an error rather than inferring a count
116
+ * from exit zero (T12308).
117
+ */
75
118
  minCount?: number;
76
119
  /** Working directory relative to project root. Default `.`. */
77
120
  cwd?: string;
@@ -181,8 +224,8 @@ export interface LintGate extends GateBase {
181
224
 
182
225
  /**
183
226
  * 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.
227
+ * ship a webapp or API. Probing an existing service is supported; `startCommand`
228
+ * currently returns an explicit error pending an admitted owned service lifetime.
186
229
  */
187
230
  export interface HttpGate extends GateBase {
188
231
  kind: 'http';
@@ -319,16 +362,92 @@ export type GateResultDetails =
319
362
 
320
363
  // ─── Result ──────────────────────────────────────────────────────────────────
321
364
 
365
+ /**
366
+ * Exact bytes observed for one declared task input or resolved verification harness.
367
+ * @remarks A missing input is explicit, with both digest and size null. This inventory
368
+ * does not claim coverage of undeclared runtime dependencies or external services.
369
+ * @example
370
+ * ```typescript
371
+ * const input: AcceptanceGateArtifact = { path: '/project/check.mjs', sha256: 'a'.repeat(64), bytes: 42 };
372
+ * ```
373
+ */
374
+ export interface AcceptanceGateArtifact {
375
+ /** Captured absolute input path, resolved within the admitted project scope. */
376
+ path: string;
377
+ /** SHA-256 of actual input bytes; null only for an observed absent path. */
378
+ sha256: string | null;
379
+ /** Exact byte length, or null alongside an absent digest. */
380
+ bytes: number | null;
381
+ }
382
+
383
+ /**
384
+ * Executable invocation captured before launching a typed verification gate.
385
+ * @remarks Environment values are represented only by a digest; credentials must
386
+ * not be duplicated into receipts. The verifier compares actual launch inputs.
387
+ * @example
388
+ * ```typescript
389
+ * const invocation: AcceptanceGateInvocation = { command: 'node', args: ['check.mjs'], cwd: '/project', environmentHash: 'b'.repeat(64) };
390
+ * ```
391
+ */
392
+ export interface AcceptanceGateInvocation {
393
+ /** Exact executable passed to the existing process port. */
394
+ command: string;
395
+ /** Ordered arguments, without shell re-interpretation. */
396
+ args: string[];
397
+ /** Captured absolute process working directory. */
398
+ cwd: string;
399
+ /** SHA-256 of the canonically ordered effective environment. */
400
+ environmentHash: string;
401
+ }
402
+
403
+ /**
404
+ * Task and input binding attached to an explicitly verified acceptance result.
405
+ * @remarks This is provenance, not authority by itself. Persistence and completion
406
+ * must compare current canonical task/AC rows, input bytes and captured execution.
407
+ * No gate runs implicitly on task creation or editing. Historical unbound results
408
+ * remain readable but do not establish typed requirement completion.
409
+ * @example
410
+ * ```typescript
411
+ * const owner = result.binding?.identity.projectId;
412
+ * ```
413
+ */
414
+ export interface AcceptanceGateBinding {
415
+ /** Version of the captured binding contract. */
416
+ version: 1;
417
+ /** Unique verification attempt whose result and receipt commit atomically. */
418
+ verificationId: string;
419
+ /** Existing captured operation identity, including actor and retry identity. */
420
+ identity: OperationExecutionIdentity;
421
+ /** Exact canonical task owning this criterion. */
422
+ taskId: string;
423
+ /** Current normalized acceptance row identifier. */
424
+ criterionId: string;
425
+ /** SHA-256 over the criterion's canonical persisted payload. */
426
+ criterionHash: string;
427
+ /** SHA-256 over the complete canonical gate, including REQ and launch options. */
428
+ gateHash: string;
429
+ /** ISO time of input capture before execution. */
430
+ capturedAt: string;
431
+ /** Original admitted absolute deadline in epoch milliseconds; never renewed. */
432
+ deadlineAt: number;
433
+ /** Actual process inputs; required for executable gate kinds. */
434
+ invocation?: AcceptanceGateInvocation;
435
+ /** Explicit bounded input inventory, including untracked harness/task files. */
436
+ artifacts: AcceptanceGateArtifact[];
437
+ }
438
+
322
439
  /**
323
440
  * Result of running one acceptance gate.
324
441
  *
325
- * Persisted to `lifecycle_gate_results` and summarised in
326
- * `task.verification.gateResults`.
442
+ * Returned by the runner. Canonical verification explicitly persists bound results;
443
+ * the result shape alone does not prove persistence or authorize completion.
327
444
  *
328
445
  * NOTE: Named `AcceptanceGateResult` (not `GateResult`) for historical
329
446
  * clarity — it describes acceptance-criterion gate outcomes specifically.
330
447
  */
331
448
  export interface AcceptanceGateResult {
449
+ /** Explicit verification binding; absent on historical/unpersisted runner observations. */
450
+ binding?: AcceptanceGateBinding;
332
451
  /** Zero-based index in the task's acceptance array. */
333
452
  index: number;
334
453
  /** REQ-ID if the gate had one, else `undefined`. */
@@ -356,6 +475,8 @@ export interface AcceptanceGateResult {
356
475
  result: 'pass' | 'fail' | 'warn' | 'skipped' | 'error';
357
476
  /** Wall-clock duration of the gate execution in milliseconds. */
358
477
  durationMs: number;
478
+ /** Actual process outcome and containment evidence; wrapper status is not target proof. */
479
+ execution?: ProcessCaptureResult;
359
480
  /**
360
481
  * Typed kind-specific detail payload.
361
482
  *
@@ -384,3 +505,15 @@ export interface AcceptanceGateResult {
384
505
  /** Agent identifier or `"human"` that ran or attested the gate. */
385
506
  checkedBy: string;
386
507
  }
508
+
509
+ /** Canonical typed-verification audit details stored with results in the task transaction. */
510
+ export interface AcceptanceGateVerificationReceipt {
511
+ /** One admitted verification batch identity shared by every result. */
512
+ verificationId: string;
513
+ /** SHA-256 of the exact persisted result-array JSON bytes. */
514
+ resultHash: string;
515
+ /** Explicit verification operation; never inferred from generic completion evidence. */
516
+ operation: 'check.gate.verify';
517
+ /** Overall generic-plus-typed verification outcome at this observation. */
518
+ passed: boolean;
519
+ }
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
+ }