@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.
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
@@ -11,8 +11,14 @@
11
11
  * @task T780
12
12
  */
13
13
 
14
+ import { readFileSync } from 'node:fs';
14
15
  import { describe, expect, it } from 'vitest';
15
- import type { AcceptanceGate, AcceptanceGateResult, FileAssertion } from '../acceptance-gate.js';
16
+ import type {
17
+ AcceptanceGate,
18
+ AcceptanceGateBinding,
19
+ AcceptanceGateResult,
20
+ FileAssertion,
21
+ } from '../acceptance-gate.js';
16
22
  import {
17
23
  acceptanceArraySchema,
18
24
  acceptanceGateResultSchema,
@@ -23,9 +29,11 @@ import {
23
29
  httpGateSchema,
24
30
  lintGateSchema,
25
31
  manualGateSchema,
32
+ testCountReportSchema,
26
33
  testGateSchema,
27
34
  } from '../acceptance-gate-schema.js';
28
35
  import type { AcceptanceItem } from '../index.js';
36
+ import type { ProcessCaptureResult } from '../resource-governor.js';
29
37
 
30
38
  // ─── TestGate ────────────────────────────────────────────────────────────────
31
39
 
@@ -734,3 +742,276 @@ describe('acceptanceArraySchema — mixed (string | AcceptanceGate)[] (T780)', (
734
742
  expect(typed).toHaveLength(2);
735
743
  });
736
744
  });
745
+
746
+ describe('acceptance result captured execution preservation (T12292)', () => {
747
+ const execution: ProcessCaptureResult = {
748
+ started: true,
749
+ targetPid: 1234,
750
+ exitCode: 0,
751
+ signal: null,
752
+ error: null,
753
+ stopped: null,
754
+ stdout: 'literal ü | output',
755
+ stderr: '',
756
+ outputTruncated: false,
757
+ durationMs: 12,
758
+ mode: 'systemd',
759
+ unitName: 'cleo-tool-fixture.scope',
760
+ nativeMemory: 'observed-cgroup',
761
+ resourceLimits: {
762
+ cgroup: '/user.slice/cleo-tool-fixture.scope',
763
+ memoryMaxBytes: 4294967296,
764
+ tasksMax: 256,
765
+ },
766
+ cleanupScope: 'process-group',
767
+ transportClosed: true,
768
+ targetCloseObserved: true,
769
+ cleanupObservation: 'scope-terminal',
770
+ cleanupErrors: [],
771
+ };
772
+ const result: AcceptanceGateResult = {
773
+ index: 3,
774
+ req: 'PARTNER-EXACT',
775
+ kind: 'test',
776
+ result: 'pass',
777
+ durationMs: 12,
778
+ checkedAt: '2026-09-20T17:00:00.000Z',
779
+ checkedBy: 'captured-agent',
780
+ execution,
781
+ };
782
+
783
+ it('preserves every execution field through JSON and runtime validation', () => {
784
+ expect(acceptanceGateResultSchema.parse(JSON.parse(JSON.stringify(result)))).toEqual(result);
785
+ });
786
+
787
+ it.each([
788
+ 'deadline',
789
+ 'cancelled',
790
+ 'resource-limit',
791
+ 'output-limit',
792
+ 'transport-error',
793
+ ] as const)('retains %s as an incomplete observation without inventing a verdict', (stopped) => {
794
+ const interrupted: AcceptanceGateResult = {
795
+ ...result,
796
+ result: 'error',
797
+ execution: { ...execution, stopped, exitCode: null, targetCloseObserved: false },
798
+ };
799
+ expect(acceptanceGateResultSchema.parse(interrupted)).toEqual(interrupted);
800
+ expect(acceptanceGateResultSchema.safeParse({ ...interrupted, result: 'pass' }).success).toBe(
801
+ false,
802
+ );
803
+ });
804
+
805
+ it('rejects a completed verdict for an unstarted target', () => {
806
+ expect(
807
+ acceptanceGateResultSchema.safeParse({
808
+ ...result,
809
+ execution: { ...execution, started: false, targetPid: null, exitCode: null },
810
+ }).success,
811
+ ).toBe(false);
812
+ });
813
+
814
+ it('rejects malformed or contradictory process evidence instead of stripping it', () => {
815
+ for (const corrupt of [
816
+ { ...execution, unexpectedAuthority: true },
817
+ { ...execution, targetPid: 0 },
818
+ { ...execution, signal: 'SIGTERM' },
819
+ { ...execution, nativeMemory: 'observed-cgroup', resourceLimits: undefined },
820
+ { ...execution, resourceLimits: { ...execution.resourceLimits, cgroup: '/wrong-owner' } },
821
+ { ...execution, durationMs: Number.POSITIVE_INFINITY },
822
+ ])
823
+ expect(acceptanceGateResultSchema.safeParse({ ...result, execution: corrupt }).success).toBe(
824
+ false,
825
+ );
826
+ });
827
+ });
828
+
829
+ describe('typed requirement verification binding (T12292)', () => {
830
+ const binding: AcceptanceGateBinding = {
831
+ version: 1,
832
+ verificationId: '9f4a8d70-6325-4bf8-a30c-b719ade689da',
833
+ identity: {
834
+ projectId: 'stable-project',
835
+ projectRoot: '/project',
836
+ actor: 'agent-test',
837
+ operation: 'check.gate.verify',
838
+ idempotencyKey: 'verification-attempt',
839
+ },
840
+ taskId: 'T122',
841
+ criterionId: 'e1cf21de-92db-46e4-8e3d-156ab78a0b32',
842
+ criterionHash: 'a'.repeat(64),
843
+ gateHash: 'b'.repeat(64),
844
+ capturedAt: '2026-09-20T17:00:00.000Z',
845
+ deadlineAt: 1790000000000,
846
+ invocation: {
847
+ command: 'node',
848
+ args: ['scripts/verify.mjs', '--task', 'T122'],
849
+ cwd: '/project',
850
+ environmentHash: 'c'.repeat(64),
851
+ },
852
+ artifacts: [{ path: '/project/scripts/verify.mjs', sha256: 'd'.repeat(64), bytes: 81 }],
853
+ };
854
+ const result: AcceptanceGateResult = {
855
+ index: 3,
856
+ req: 'PARTNER-122',
857
+ kind: 'test',
858
+ result: 'error',
859
+ durationMs: 5,
860
+ checkedAt: '2026-09-20T17:00:00.005Z',
861
+ checkedBy: 'agent-test',
862
+ binding,
863
+ };
864
+
865
+ it('round-trips bound identity, invocation hashes and untracked input snapshots losslessly', () => {
866
+ expect(acceptanceGateResultSchema.parse(JSON.parse(JSON.stringify(result)))).toEqual(result);
867
+ });
868
+
869
+ it('preserves an explicitly observed absent input without fabricating a hash', () => {
870
+ const absent = {
871
+ ...result,
872
+ binding: {
873
+ ...binding,
874
+ artifacts: [{ path: '/project/scripts/missing.mjs', sha256: null, bytes: null }],
875
+ },
876
+ };
877
+ expect(acceptanceGateResultSchema.parse(absent)).toEqual(absent);
878
+ });
879
+
880
+ it('does not turn a bound process result into a verdict without an actual execution observation', () => {
881
+ expect(acceptanceGateResultSchema.safeParse({ ...result, result: 'pass' }).success).toBe(false);
882
+ const legacy = { ...result, binding: undefined, result: 'pass' };
883
+ expect(acceptanceGateResultSchema.parse(legacy)).toEqual(legacy);
884
+ });
885
+
886
+ it.each([
887
+ { ...binding, gateHash: 'not-a-hash' },
888
+ { ...binding, identity: { ...binding.identity, projectRoot: 'relative-root' } },
889
+ { ...binding, invocation: { ...binding.invocation, cwd: 'relative-cwd' } },
890
+ { ...binding, artifacts: [{ ...binding.artifacts[0], path: 'relative-input' }] },
891
+ { ...binding, verificationId: 'not-a-uuid' },
892
+ { ...binding, identity: { ...binding.identity, actor: 'different-agent' } },
893
+ { ...binding, capturedAt: '2026-09-20T17:00:01.000Z' },
894
+ { ...binding, deadlineAt: 1 },
895
+ { ...binding, artifacts: [binding.artifacts[0], binding.artifacts[0]] },
896
+ { ...binding, artifacts: [{ ...binding.artifacts[0], sha256: null }] },
897
+ { ...binding, artifacts: [{ ...binding.artifacts[0], bytes: -1 }] },
898
+ {
899
+ ...binding,
900
+ invocation: { ...binding.invocation, environmentHash: '', env: { SECRET: 'must-not-store' } },
901
+ },
902
+ { ...binding, authority: 'implicit' },
903
+ ])('rejects inconsistent or malformed persisted binding %#', (invalid) => {
904
+ expect(acceptanceGateResultSchema.safeParse({ ...result, binding: invalid }).success).toBe(
905
+ false,
906
+ );
907
+ });
908
+ });
909
+
910
+ describe('published acceptance result schema (T12292)', () => {
911
+ it('retains binding and execution shapes while disclosing runtime-only invariants', () => {
912
+ const emitted = JSON.parse(
913
+ readFileSync(new URL('../../schemas/gate-result.schema.json', import.meta.url), 'utf8'),
914
+ );
915
+ expect(emitted.properties.binding.properties.gateHash.pattern).toBe('^[a-f0-9]{64}$');
916
+ expect(emitted.properties.binding.properties.artifacts.items.properties.sha256).toBeDefined();
917
+ expect(emitted.properties.execution.properties.cleanupObservation).toBeDefined();
918
+ expect(emitted.properties.binding.additionalProperties).toBe(false);
919
+ expect(emitted.$comment).toContain('acceptanceGateResultSchema');
920
+ expect(emitted.$comment).toContain('cross-field');
921
+ expect(emitted.$comment).toContain('not proof');
922
+ expect(emitted.required).not.toContain('binding');
923
+ expect(emitted.required).not.toContain('execution');
924
+ });
925
+ });
926
+
927
+ describe('structured minimum-count report schema', () => {
928
+ // Captured Vitest 4.1.4 JSON: one nested suite, pass + skip + todo.
929
+ const report = {
930
+ numTotalTestSuites: 2,
931
+ numPassedTestSuites: 2,
932
+ numFailedTestSuites: 0,
933
+ numPendingTestSuites: 0,
934
+ numTotalTests: 3,
935
+ numPassedTests: 1,
936
+ numFailedTests: 0,
937
+ numPendingTests: 1,
938
+ numTodoTests: 1,
939
+ success: true,
940
+ testResults: [
941
+ {
942
+ name: '/fixture/reporter.test.ts',
943
+ status: 'passed',
944
+ assertionResults: [
945
+ { fullName: 'actual reporter passes', status: 'passed' },
946
+ { fullName: 'actual reporter skipped', status: 'skipped' },
947
+ { fullName: 'actual reporter later', status: 'todo' },
948
+ ],
949
+ },
950
+ ],
951
+ };
952
+
953
+ it('recognizes passed tests without counting skipped or todo assertions', () => {
954
+ const parsed = testCountReportSchema.parse(report);
955
+ expect(parsed.numPassedTests).toBe(1);
956
+ expect(parsed.numTotalTests).toBe(3);
957
+ expect(parsed.numTotalTestSuites).toBe(2);
958
+ expect(parsed.testResults).toHaveLength(1);
959
+ });
960
+
961
+ it.each([
962
+ { numPassedTests: 3 },
963
+ { numTotalTests: 99 },
964
+ { numPendingTests: 0 },
965
+ { numTodoTests: 0 },
966
+ { numPassedTests: 1.5 },
967
+ { numPassedTests: -1 },
968
+ { numTotalTests: Number.MAX_SAFE_INTEGER + 1 },
969
+ { numFailedTestSuites: 1 },
970
+ ])('rejects malformed or inconsistent counters: %j', (change) => {
971
+ expect(testCountReportSchema.safeParse({ ...report, ...change }).success).toBe(false);
972
+ });
973
+
974
+ it('rejects summary-only, missing counters, and unsupported assertion states', () => {
975
+ expect(testCountReportSchema.safeParse({ numPassedTests: 100, success: true }).success).toBe(
976
+ false,
977
+ );
978
+ expect(testCountReportSchema.safeParse({ ...report, numTodoTests: undefined }).success).toBe(
979
+ false,
980
+ );
981
+ expect(
982
+ testCountReportSchema.safeParse({
983
+ ...report,
984
+ testResults: [
985
+ {
986
+ ...report.testResults[0],
987
+ assertionResults: [{ fullName: 'test', status: 'invented' }],
988
+ },
989
+ ],
990
+ }).success,
991
+ ).toBe(false);
992
+ });
993
+
994
+ it('retains real failed suite reports without converting them into success', () => {
995
+ const failed = {
996
+ ...report,
997
+ success: false,
998
+ numPassedTestSuites: 1,
999
+ numFailedTestSuites: 1,
1000
+ testResults: [{ ...report.testResults[0], status: 'failed' }],
1001
+ };
1002
+ expect(testCountReportSchema.parse(failed).success).toBe(false);
1003
+ expect(testCountReportSchema.safeParse({ ...failed, success: true }).success).toBe(false);
1004
+ });
1005
+
1006
+ it('accepts zero-count structure without claiming that any test ran', () => {
1007
+ const empty = {
1008
+ ...report,
1009
+ numTotalTests: 0,
1010
+ numPassedTests: 0,
1011
+ numPendingTests: 0,
1012
+ numTodoTests: 0,
1013
+ testResults: [],
1014
+ };
1015
+ expect(testCountReportSchema.parse(empty).numPassedTests).toBe(0);
1016
+ });
1017
+ });
@@ -540,7 +540,10 @@ describe('GATE_EVIDENCE_REQUIREMENTS (T10337)', () => {
540
540
  ];
541
541
  expect(validateEvidenceForGate('implemented', d).ok).toBe(true);
542
542
 
543
- const e: EvidenceAtomInput[] = [{ kind: 'pr', prNumber: 357 }];
543
+ const e: EvidenceAtomInput[] = [
544
+ { kind: 'pr', prNumber: 357 },
545
+ { kind: 'files', paths: ['src/fix.ts'] },
546
+ ];
544
547
  expect(validateEvidenceForGate('implemented', e).ok).toBe(true);
545
548
  });
546
549
 
@@ -552,11 +555,11 @@ describe('GATE_EVIDENCE_REQUIREMENTS (T10337)', () => {
552
555
  if (!r.ok) {
553
556
  expect(r.message).toMatch(/^Gate 'implemented' requires evidence: /);
554
557
  expect(r.message).toContain('[commit AND files]');
555
- expect(r.message).toContain('[pr]');
558
+ expect(r.message).toContain('[pr AND files]');
556
559
  }
557
560
  });
558
561
 
559
- it('testsPassed accepts test-run, tool, pr — single-atom alternatives', () => {
562
+ it('testsPassed accepts actual runs and tools but rejects PR provenance', () => {
560
563
  expect(
561
564
  validateEvidenceForGate('testsPassed', [
562
565
  { kind: 'test-run', path: '/tmp/v.json' },
@@ -570,7 +573,7 @@ describe('GATE_EVIDENCE_REQUIREMENTS (T10337)', () => {
570
573
  expect(
571
574
  validateEvidenceForGate('testsPassed', [{ kind: 'pr', prNumber: 357 }] as EvidenceAtomInput[])
572
575
  .ok,
573
- ).toBe(true);
576
+ ).toBe(false);
574
577
  });
575
578
 
576
579
  it('testsPassed rejects empty evidence', () => {
@@ -579,7 +582,7 @@ describe('GATE_EVIDENCE_REQUIREMENTS (T10337)', () => {
579
582
 
580
583
  it('qaPassed accepts a single tool atom (behavior parity with legacy)', () => {
581
584
  // Critical parity check: GATE_EVIDENCE_REQUIREMENTS.qaPassed.oneOf is
582
- // [['tool'], ['pr']] — not [['tool', 'tool']]. The legacy runtime did NOT
585
+ // [['tool']] — not [['tool', 'tool']]. The legacy runtime did NOT
583
586
  // count distinct tool names, so one tool atom passes.
584
587
  expect(
585
588
  validateEvidenceForGate('qaPassed', [{ kind: 'tool', tool: 'lint' }] as EvidenceAtomInput[])
@@ -587,11 +590,11 @@ describe('GATE_EVIDENCE_REQUIREMENTS (T10337)', () => {
587
590
  ).toBe(true);
588
591
  });
589
592
 
590
- it('qaPassed also accepts pr atom', () => {
593
+ it('qaPassed rejects PR provenance without a review tool result', () => {
591
594
  expect(
592
595
  validateEvidenceForGate('qaPassed', [{ kind: 'pr', prNumber: 357 }] as EvidenceAtomInput[])
593
596
  .ok,
594
- ).toBe(true);
597
+ ).toBe(false);
595
598
  });
596
599
 
597
600
  it('documented accepts files or url', () => {
@@ -656,7 +659,7 @@ describe('formatGateRequirement (T10337)', () => {
656
659
  expect(s).toContain('[commit AND note]');
657
660
  expect(s).toContain('[decision AND files]');
658
661
  expect(s).toContain('[decision AND note]');
659
- expect(s).toContain('[pr]');
662
+ expect(s).toContain('[pr AND files]');
660
663
  // Joiner between combinations is ' OR '.
661
664
  expect(s.split(' OR ').length).toBe(5);
662
665
  });
@@ -678,7 +681,7 @@ describe('formatGateRequirementHint (T9949)', () => {
678
681
  expect(h).toContain("--evidence 'commit:<sha>;note:<short description>'");
679
682
  expect(h).toContain("--evidence 'decision:D-arch-001;files:path/a.ts,path/b.ts'");
680
683
  expect(h).toContain("--evidence 'decision:D-arch-001;note:<short description>'");
681
- expect(h).toContain("--evidence 'pr:357'");
684
+ expect(h).toContain("--evidence 'pr:357;files:path/a.ts,path/b.ts'");
682
685
  // Routes a `note:`-only caller at the partner atom they need.
683
686
  expect(h).toContain("'note:' alone is NOT accepted for this gate");
684
687
  expect(h).toContain("'commit:'");
@@ -686,11 +689,11 @@ describe('formatGateRequirementHint (T9949)', () => {
686
689
  });
687
690
 
688
691
  it('routes note-only callers for testsPassed at the right alternatives', () => {
689
- // testsPassed accepts test-run, tool, or pr — none of which involve note.
692
+ // testsPassed accepts test-run or tool — neither involves note.
690
693
  const h = formatGateRequirementHint('testsPassed');
691
694
  expect(h).toContain("--evidence 'test-run:/tmp/vitest-out.json'");
692
695
  expect(h).toContain("--evidence 'tool:test'");
693
- expect(h).toContain("--evidence 'pr:357'");
696
+ expect(h).not.toContain('pr:357');
694
697
  // When no combination includes note at all, the helper falls through to a
695
698
  // generic "not accepted, see ADR-051" clarifier rather than fabricating a
696
699
  // partner suggestion.
@@ -731,9 +734,9 @@ describe('validateEvidenceForGate failure surface (T9949)', () => {
731
734
  ] as EvidenceAtomInput[]);
732
735
  expect(r.ok).toBe(false);
733
736
  if (!r.ok) {
734
- // Legacy single-line message preserved byte-for-byte.
737
+ // Error includes the same artifact requirement as the validator.
735
738
  expect(r.message).toBe(
736
- "Gate 'implemented' requires evidence: [commit AND files] OR [commit AND note] OR [decision AND files] OR [decision AND note] OR [pr]",
739
+ "Gate 'implemented' requires evidence: [commit AND files] OR [commit AND note] OR [decision AND files] OR [decision AND note] OR [pr AND files]",
737
740
  );
738
741
  // New rich hint includes the same content as formatGateRequirementHint.
739
742
  expect(r.hint).toBe(formatGateRequirementHint('implemented'));
@@ -747,7 +750,7 @@ describe('validateEvidenceForGate failure surface (T9949)', () => {
747
750
  expect(r.ok).toBe(false);
748
751
  if (!r.ok) {
749
752
  expect(r.hint).toContain("--evidence 'tool:test'");
750
- expect(r.hint).toContain("--evidence 'pr:357'");
753
+ expect(r.hint).not.toContain('pr:357');
751
754
  expect(r.hint).toContain("'note:' is NOT accepted for this gate");
752
755
  }
753
756
  });
@@ -58,6 +58,7 @@ type AssertEquals1<T extends 1> = T;
58
58
  // ─── Search: BrainCompactHit shape pin ──────────────────────────────
59
59
 
60
60
  type _BrainCompactHitShape = {
61
+ matchedHistoricalIds?: string[];
61
62
  id: string;
62
63
  type: 'decision' | 'pattern' | 'learning' | 'observation';
63
64
  title: string;
@@ -73,6 +74,7 @@ type _AssertBrainCompactHitPinned = AssertEquals1<Equals<BrainCompactHit, _Brain
73
74
  // ─── Search: SearchBrainCompactParams shape pin ─────────────────────
74
75
 
75
76
  type _SearchBrainCompactParamsShape = {
77
+ includeHistory?: boolean;
76
78
  query: string;
77
79
  limit?: number;
78
80
  tables?: Array<'decisions' | 'patterns' | 'learnings' | 'observations'>;
@@ -20,6 +20,19 @@ describe('operations-registry', () => {
20
20
  expect(JSON.stringify(OPERATIONS)).toMatchSnapshot();
21
21
  });
22
22
 
23
+ it('declares deletion force and cascade as distinct optional boolean controls', () => {
24
+ const deletion = OPERATIONS.find(
25
+ (operation) => operation.domain === 'tasks' && operation.operation === 'delete',
26
+ );
27
+ expect(deletion?.requiredParams).toEqual(['taskId']);
28
+ expect(deletion?.params).toEqual(
29
+ expect.arrayContaining([
30
+ expect.objectContaining({ name: 'force', type: 'boolean', required: false }),
31
+ expect.objectContaining({ name: 'cascade', type: 'boolean', required: false }),
32
+ ]),
33
+ );
34
+ });
35
+
23
36
  it('OPERATIONS count is consistent', () => {
24
37
  expect(OPERATIONS.length).toBeGreaterThan(0);
25
38
  const queryCount = OPERATIONS.filter((o) => o.gateway === 'query').length;
@@ -40,6 +53,19 @@ describe('operations-registry', () => {
40
53
  }
41
54
  });
42
55
 
56
+ it('registers docs.status as a read-only, session-independent query', () => {
57
+ const matches = OPERATIONS.filter(
58
+ (operation) => operation.domain === 'docs' && operation.operation === 'status',
59
+ );
60
+ expect(matches).toHaveLength(1);
61
+ expect(matches[0]).toMatchObject({
62
+ gateway: 'query',
63
+ idempotent: true,
64
+ sessionRequired: false,
65
+ requiredParams: [],
66
+ });
67
+ });
68
+
43
69
  it('defineOp is an identity function', () => {
44
70
  const def = OPERATIONS[0];
45
71
  expect(defineOp(def)).toBe(def);