@cleocode/contracts 2026.5.111 → 2026.5.113

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 (118) hide show
  1. package/dist/__tests__/cli-meta-suggested-next.test.d.ts +21 -0
  2. package/dist/__tests__/cli-meta-suggested-next.test.d.ts.map +1 -0
  3. package/dist/__tests__/cli-meta-suggested-next.test.js +70 -0
  4. package/dist/__tests__/cli-meta-suggested-next.test.js.map +1 -0
  5. package/dist/__tests__/evidence-atom-schema.test.d.ts +16 -0
  6. package/dist/__tests__/evidence-atom-schema.test.d.ts.map +1 -0
  7. package/dist/__tests__/evidence-atom-schema.test.js +394 -0
  8. package/dist/__tests__/evidence-atom-schema.test.js.map +1 -0
  9. package/dist/config/__tests__/manifest.test.d.ts +13 -0
  10. package/dist/config/__tests__/manifest.test.d.ts.map +1 -0
  11. package/dist/config/__tests__/manifest.test.js +143 -0
  12. package/dist/config/__tests__/manifest.test.js.map +1 -0
  13. package/dist/config/manifest.d.ts +158 -0
  14. package/dist/config/manifest.d.ts.map +1 -0
  15. package/dist/config/manifest.js +123 -0
  16. package/dist/config/manifest.js.map +1 -0
  17. package/dist/docs/__tests__/provenance.test.d.ts +14 -0
  18. package/dist/docs/__tests__/provenance.test.d.ts.map +1 -0
  19. package/dist/docs/__tests__/provenance.test.js +579 -0
  20. package/dist/docs/__tests__/provenance.test.js.map +1 -0
  21. package/dist/docs/provenance.d.ts +731 -0
  22. package/dist/docs/provenance.d.ts.map +1 -0
  23. package/dist/docs/provenance.js +258 -0
  24. package/dist/docs/provenance.js.map +1 -0
  25. package/dist/docs-taxonomy.d.ts +24 -0
  26. package/dist/docs-taxonomy.d.ts.map +1 -1
  27. package/dist/docs-taxonomy.js +27 -0
  28. package/dist/docs-taxonomy.js.map +1 -1
  29. package/dist/doctor.d.ts +101 -0
  30. package/dist/doctor.d.ts.map +1 -1
  31. package/dist/errors.d.ts +4 -0
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +4 -0
  34. package/dist/errors.js.map +1 -1
  35. package/dist/evidence-atom-schema.d.ts +407 -0
  36. package/dist/evidence-atom-schema.d.ts.map +1 -0
  37. package/dist/evidence-atom-schema.js +505 -0
  38. package/dist/evidence-atom-schema.js.map +1 -0
  39. package/dist/exit-codes.d.ts +37 -0
  40. package/dist/exit-codes.d.ts.map +1 -1
  41. package/dist/exit-codes.js +37 -0
  42. package/dist/exit-codes.js.map +1 -1
  43. package/dist/index.d.ts +13 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +8 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/invariants/__tests__/adr-056-release.test.d.ts +16 -0
  48. package/dist/invariants/__tests__/adr-056-release.test.d.ts.map +1 -0
  49. package/dist/invariants/__tests__/adr-056-release.test.js +88 -0
  50. package/dist/invariants/__tests__/adr-056-release.test.js.map +1 -0
  51. package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts +22 -0
  52. package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts.map +1 -0
  53. package/dist/invariants/__tests__/adr-070-orchestration.test.js +151 -0
  54. package/dist/invariants/__tests__/adr-070-orchestration.test.js.map +1 -0
  55. package/dist/invariants/__tests__/registry.test.d.ts +14 -0
  56. package/dist/invariants/__tests__/registry.test.d.ts.map +1 -0
  57. package/dist/invariants/__tests__/registry.test.js +113 -0
  58. package/dist/invariants/__tests__/registry.test.js.map +1 -0
  59. package/dist/invariants/adr-056-release.d.ts +42 -0
  60. package/dist/invariants/adr-056-release.d.ts.map +1 -0
  61. package/dist/invariants/adr-056-release.js +143 -0
  62. package/dist/invariants/adr-056-release.js.map +1 -0
  63. package/dist/invariants/adr-070-orchestration.d.ts +73 -0
  64. package/dist/invariants/adr-070-orchestration.d.ts.map +1 -0
  65. package/dist/invariants/adr-070-orchestration.js +290 -0
  66. package/dist/invariants/adr-070-orchestration.js.map +1 -0
  67. package/dist/invariants/adr-073-saga.d.ts +32 -0
  68. package/dist/invariants/adr-073-saga.d.ts.map +1 -0
  69. package/dist/invariants/adr-073-saga.js +155 -0
  70. package/dist/invariants/adr-073-saga.js.map +1 -0
  71. package/dist/invariants/index.d.ts +162 -0
  72. package/dist/invariants/index.d.ts.map +1 -0
  73. package/dist/invariants/index.js +118 -0
  74. package/dist/invariants/index.js.map +1 -0
  75. package/dist/operations/__tests__/input-contract.test.d.ts +20 -0
  76. package/dist/operations/__tests__/input-contract.test.d.ts.map +1 -0
  77. package/dist/operations/__tests__/input-contract.test.js +158 -0
  78. package/dist/operations/__tests__/input-contract.test.js.map +1 -0
  79. package/dist/operations/docs.d.ts +16 -0
  80. package/dist/operations/docs.d.ts.map +1 -1
  81. package/dist/operations/input-contract.d.ts +267 -0
  82. package/dist/operations/input-contract.d.ts.map +1 -0
  83. package/dist/operations/input-contract.js +29 -0
  84. package/dist/operations/input-contract.js.map +1 -0
  85. package/dist/templates/__tests__/manifest.test.d.ts +18 -0
  86. package/dist/templates/__tests__/manifest.test.d.ts.map +1 -0
  87. package/dist/templates/__tests__/manifest.test.js +123 -0
  88. package/dist/templates/__tests__/manifest.test.js.map +1 -0
  89. package/dist/templates/manifest.d.ts +186 -0
  90. package/dist/templates/manifest.d.ts.map +1 -0
  91. package/dist/templates/manifest.js +165 -0
  92. package/dist/templates/manifest.js.map +1 -0
  93. package/package.json +2 -2
  94. package/src/__tests__/cli-meta-suggested-next.test.ts +76 -0
  95. package/src/__tests__/evidence-atom-schema.test.ts +499 -0
  96. package/src/config/__tests__/manifest.test.ts +176 -0
  97. package/src/config/manifest.ts +218 -0
  98. package/src/docs/__tests__/__snapshots__/provenance.test.ts.snap +252 -0
  99. package/src/docs/__tests__/provenance.test.ts +648 -0
  100. package/src/docs/provenance.ts +601 -0
  101. package/src/docs-taxonomy.ts +55 -0
  102. package/src/doctor.ts +111 -0
  103. package/src/errors.ts +4 -0
  104. package/src/evidence-atom-schema.ts +628 -0
  105. package/src/exit-codes.ts +39 -0
  106. package/src/index.ts +129 -0
  107. package/src/invariants/__tests__/adr-056-release.test.ts +102 -0
  108. package/src/invariants/__tests__/adr-070-orchestration.test.ts +186 -0
  109. package/src/invariants/__tests__/registry.test.ts +136 -0
  110. package/src/invariants/adr-056-release.ts +154 -0
  111. package/src/invariants/adr-070-orchestration.ts +317 -0
  112. package/src/invariants/adr-073-saga.ts +167 -0
  113. package/src/invariants/index.ts +222 -0
  114. package/src/operations/__tests__/input-contract.test.ts +188 -0
  115. package/src/operations/docs.ts +16 -0
  116. package/src/operations/input-contract.ts +301 -0
  117. package/src/templates/__tests__/manifest.test.ts +140 -0
  118. package/src/templates/manifest.ts +208 -0
package/src/index.ts CHANGED
@@ -224,6 +224,21 @@ export type {
224
224
  ConduitTopicSubscribeOptions,
225
225
  ConduitUnsubscribe,
226
226
  } from './conduit.js';
227
+ // === Config Manifest (T9876 / Saga T9855) ===
228
+ export type {
229
+ ConfigManifestEntry,
230
+ ConfigManifestScope,
231
+ ConfigScope,
232
+ DriftDetection,
233
+ } from './config/manifest.js';
234
+ export {
235
+ CLEO_CONFIG_MANIFEST,
236
+ CONFIG_MANIFEST_ENTRIES,
237
+ configManifestEntrySchema,
238
+ GLOBAL_CLEO_CONFIG_MANIFEST,
239
+ PROJECT_CONTEXT_MANIFEST,
240
+ PROJECT_INFO_MANIFEST,
241
+ } from './config/manifest.js';
227
242
  // === Configuration Types ===
228
243
  export type {
229
244
  BackupConfig,
@@ -312,6 +327,42 @@ export { CANONICAL_DOMAINS } from './dispatch/identity.js';
312
327
  export type { OperationDef, Resolution } from './dispatch/operation-def.js';
313
328
  // === Dispatch OPERATIONS data + builder helpers (T10061 — T9833b / E-CLI-BOUNDARY / SG-ARCH-SOLID) ===
314
329
  export { defineDomain, defineOp, OPERATIONS } from './dispatch/operations-registry.js';
330
+ // === Docs Provenance Graph (T10166 / Saga T9855 / E12) ===
331
+ export type {
332
+ DocLifecycleStatus,
333
+ DocProvenanceResponse,
334
+ ProvenanceDecisionNode,
335
+ ProvenanceDocNode,
336
+ ProvenanceEdge,
337
+ ProvenanceEdgeRelation,
338
+ ProvenanceMemoryNode,
339
+ ProvenanceNode,
340
+ ProvenanceNodeKind,
341
+ ProvenanceSessionNode,
342
+ ProvenanceTaskNode,
343
+ } from './docs/provenance.js';
344
+ export {
345
+ DOC_LIFECYCLE_STATUSES,
346
+ docLifecycleStatusSchema,
347
+ docProvenanceResponseSchema,
348
+ isDocProvenanceResponse,
349
+ isProvenanceDecisionNode,
350
+ isProvenanceDocNode,
351
+ isProvenanceMemoryNode,
352
+ isProvenanceSessionNode,
353
+ isProvenanceTaskNode,
354
+ PROVENANCE_EDGE_RELATIONS,
355
+ PROVENANCE_NODE_KINDS,
356
+ provenanceDecisionNodeSchema,
357
+ provenanceDocNodeSchema,
358
+ provenanceEdgeRelationSchema,
359
+ provenanceEdgeSchema,
360
+ provenanceMemoryNodeSchema,
361
+ provenanceNodeKindSchema,
362
+ provenanceNodeSchema,
363
+ provenanceSessionNodeSchema,
364
+ provenanceTaskNodeSchema,
365
+ } from './docs/provenance.js';
315
366
  // === DocsAccessor Contracts (T9063) ===
316
367
  export type {
317
368
  DocExportFormat,
@@ -342,6 +393,7 @@ export {
342
393
  // === Doctor: DB-Substrate Survey (T10307 — Saga T10281 / Epic T10282) ===
343
394
  // === Doctor: Legacy-Backup Walker (T10309 — Saga T10281 / Epic T10282) ===
344
395
  // === Doctor: Cross-DB Invariants (T10323 — Saga T10281 / Epic T10285) ===
396
+ // === Doctor: Invariant Registry Audit (T10340 — Saga T10326 / Epic T10327) ===
345
397
  export type {
346
398
  ComprehensiveAuditResult,
347
399
  DbCrossDbInvariantId,
@@ -356,6 +408,10 @@ export type {
356
408
  DbSubstrateSurveyOptions,
357
409
  DbSubstrateWarning,
358
410
  DbSubstrateWarningKind,
411
+ InvariantAuditEntry,
412
+ InvariantAuditResult,
413
+ InvariantAuditStatus,
414
+ InvariantAuditViolation,
359
415
  LegacyBackupEntry,
360
416
  LegacyBackupOriginHint,
361
417
  LegacyBackupRecommendation,
@@ -415,6 +471,31 @@ export {
415
471
  normalizeError,
416
472
  ThinAgentViolationError,
417
473
  } from './errors.js';
474
+ // === ADR-051 Evidence Atom Grammar (T10337 — Saga T10326) ===
475
+ export type {
476
+ EvidenceAtom as EvidenceAtomInput,
477
+ EvidenceAtomKind,
478
+ EvidenceValidationResult,
479
+ GateEvidenceRequirement,
480
+ } from './evidence-atom-schema.js';
481
+ export {
482
+ callsiteCoverageAtomSchema,
483
+ commitAtomSchema,
484
+ decisionAtomSchema,
485
+ EvidenceAtomSchema,
486
+ EvidenceParseError,
487
+ filesAtomSchema,
488
+ formatGateRequirement,
489
+ GATE_EVIDENCE_REQUIREMENTS,
490
+ locDropAtomSchema,
491
+ noteAtomSchema,
492
+ parseEvidenceString,
493
+ prAtomSchema,
494
+ testRunAtomSchema,
495
+ toolAtomSchema,
496
+ urlAtomSchema,
497
+ validateEvidenceForGate,
498
+ } from './evidence-atom-schema.js';
418
499
  // === Evidence Record Types (IVTR typed proof artifacts) ===
419
500
  export type {
420
501
  CommandOutputRecord,
@@ -455,6 +536,9 @@ export {
455
536
  E_EVIDENCE_INSUFFICIENT,
456
537
  // SPEC-T9345 release pipeline v2 error code names (T9530)
457
538
  E_GH_NOT_AUTHENTICATED,
539
+ // T10341 — typed CLI-layer validation for --severity + --pipeline-stage
540
+ E_INVALID_PIPELINE_STAGE,
541
+ E_INVALID_SEVERITY_VALUE,
458
542
  E_INVALID_STATE,
459
543
  E_PLAN_NOT_FOUND,
460
544
  E_RELEASE_PLAN_INVALID,
@@ -529,6 +613,21 @@ export {
529
613
  } from './graph.js';
530
614
  export type { AdapterHookProvider } from './hooks.js';
531
615
  export type { AdapterInstallProvider, InstallOptions, InstallResult } from './install.js';
616
+ // === Invariants Registry (Saga T10326 / Epic T10327 / Task T10335 — SG-SUBSTRATE-RECONCILIATION) ===
617
+ export type {
618
+ InvariantDoctorAudit,
619
+ InvariantLintRule,
620
+ InvariantRuntimeGate,
621
+ InvariantSeverity,
622
+ RegisteredInvariant,
623
+ } from './invariants/index.js';
624
+ export {
625
+ ADR_056_INVARIANTS,
626
+ ADR_073_INVARIANTS,
627
+ getInvariant,
628
+ getInvariantsByAdr,
629
+ INVARIANTS_REGISTRY,
630
+ } from './invariants/index.js';
532
631
  // === Background Job Status (T9955 — promoted from core/store/tasks-schema.ts) ===
533
632
  export type { BackgroundJobStatus } from './jobs.js';
534
633
  export type {
@@ -816,6 +915,17 @@ export type {
816
915
  } from './operations/dialectic.js';
817
916
  // === Operations Types (API wire format, namespaced to avoid collision with domain types) ===
818
917
  export * as ops from './operations/index.js';
918
+ // === Operation Input Contracts (T9914 / Saga T9855 / E7) ===
919
+ // Re-exported at top level so SDK consumers (validator T9915, mutate DX
920
+ // surface, generated docs) can import without the `ops.` namespace hop.
921
+ export type {
922
+ JsonSchema,
923
+ OperationInputContract,
924
+ OperationInputContractRegistry,
925
+ OperationInputExample,
926
+ ValidationError,
927
+ ValidationResult,
928
+ } from './operations/input-contract.js';
819
929
  // === Lifecycle Operation Types (T1455 — ADR-057 D1 Core normalization) ===
820
930
  // Re-exported at top level so @cleocode/core/lifecycle can import without the `ops.` namespace hop.
821
931
  // Note: Gate is included here — GateStatus conflict resolved (T1694): lifecycle.ts now re-exports
@@ -1805,6 +1915,25 @@ export type {
1805
1915
  TaskViewNextAction,
1806
1916
  TaskViewPipelineStage,
1807
1917
  } from './tasks.js';
1918
+ // === Template Manifest (T9875 / Saga T9855) ===
1919
+ export type {
1920
+ PlaceholderSource,
1921
+ PlaceholderSpec,
1922
+ PlaceholderSpecInput,
1923
+ TemplateKind,
1924
+ TemplateManifestEntry,
1925
+ TemplateManifestEntryInput,
1926
+ TemplateSubstitution,
1927
+ UpdateStrategy,
1928
+ } from './templates/manifest.js';
1929
+ export {
1930
+ PLACEHOLDER_SOURCES,
1931
+ PlaceholderSpecSchema,
1932
+ TEMPLATE_KINDS,
1933
+ TEMPLATE_SUBSTITUTIONS,
1934
+ TEMPLATE_UPDATE_STRATEGIES,
1935
+ TemplateManifestEntrySchema,
1936
+ } from './templates/manifest.js';
1808
1937
  // === Tessera Types ===
1809
1938
  export type {
1810
1939
  TesseraInstantiationInput,
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Unit tests for the ADR-056 D1-D6 entries in the central invariants
3
+ * registry (T10339 — R5).
4
+ *
5
+ * The release-side executable subsystem at
6
+ * `packages/core/src/release/invariants/registry.ts` consumes these
7
+ * metadata entries via `getInvariantsByAdr('ADR-056')`. The tests below
8
+ * assert the metadata shape downstream consumers (R4 CI gate, R6 doctor
9
+ * audit, R8 docs renderer) rely on.
10
+ *
11
+ * @epic T10327 — E-INVARIANT-REGISTRY-SSOT
12
+ * @saga T10326 — SG-SUBSTRATE-RECONCILIATION
13
+ * @task T10339 — R5: release-registry consumes central substrate
14
+ */
15
+
16
+ import { describe, expect, it } from 'vitest';
17
+ import {
18
+ ADR_056_INVARIANTS,
19
+ getInvariant,
20
+ getInvariantsByAdr,
21
+ INVARIANTS_REGISTRY,
22
+ } from '../index.js';
23
+
24
+ describe('ADR-056 invariants — central registry entries', () => {
25
+ it('registers all six ADR-056 decisions D1-D6 under predictable keys', () => {
26
+ for (let i = 1; i <= 6; i++) {
27
+ const key = `ADR-056.D${i}`;
28
+ expect(INVARIANTS_REGISTRY[key]).toBeDefined();
29
+ expect(INVARIANTS_REGISTRY[key]?.adr).toBe('ADR-056');
30
+ expect(INVARIANTS_REGISTRY[key]?.code).toBe(`D${i}`);
31
+ }
32
+ });
33
+
34
+ it('getInvariantsByAdr("ADR-056") returns D1..D6 in declaration order', () => {
35
+ const adr056 = getInvariantsByAdr('ADR-056');
36
+ expect(adr056).toHaveLength(6);
37
+ expect(adr056.map((e) => e.code)).toEqual(['D1', 'D2', 'D3', 'D4', 'D5', 'D6']);
38
+ });
39
+
40
+ it('ADR_056_INVARIANTS convenience binding matches getInvariantsByAdr output', () => {
41
+ expect(ADR_056_INVARIANTS).toHaveLength(6);
42
+ const enumerated = getInvariantsByAdr('ADR-056');
43
+ expect(enumerated).toHaveLength(ADR_056_INVARIANTS.length);
44
+ for (let i = 0; i < ADR_056_INVARIANTS.length; i++) {
45
+ expect(enumerated[i]?.code).toBe(ADR_056_INVARIANTS[i]?.code);
46
+ }
47
+ });
48
+
49
+ it('every entry carries a non-empty name and description', () => {
50
+ for (const entry of ADR_056_INVARIANTS) {
51
+ expect(entry.name.length).toBeGreaterThan(0);
52
+ expect(entry.description.length).toBeGreaterThan(0);
53
+ }
54
+ });
55
+
56
+ it('D4 is the archive-reason runtime gate (severity:error)', () => {
57
+ const d4 = getInvariant('ADR-056.D4');
58
+ expect(d4).toBeDefined();
59
+ expect(d4?.severity).toBe('error');
60
+ expect(d4?.runtimeGate).not.toBeNull();
61
+ expect(d4?.runtimeGate?.functionName).toBe('assertArchiveReason');
62
+ expect(d4?.runtimeGate?.module).toBe('packages/contracts/src/tasks/archive.ts');
63
+ });
64
+
65
+ it('D5 points at the release-invariants registry runtime gate', () => {
66
+ const d5 = getInvariant('ADR-056.D5');
67
+ expect(d5).toBeDefined();
68
+ expect(d5?.severity).toBe('warning');
69
+ expect(d5?.runtimeGate).not.toBeNull();
70
+ expect(d5?.runtimeGate?.functionName).toBe('runInvariants');
71
+ expect(d5?.runtimeGate?.module).toBe('packages/core/src/release/invariants/registry.ts');
72
+ });
73
+
74
+ it('D6 carries a lintRule pointer to the commit-msg hook (info severity)', () => {
75
+ const d6 = getInvariant('ADR-056.D6');
76
+ expect(d6).toBeDefined();
77
+ expect(d6?.severity).toBe('info');
78
+ expect(d6?.runtimeGate).toBeNull();
79
+ expect(d6?.lintRule?.lintScript).toBe('scripts/hooks/commit-msg-release-lint.mjs');
80
+ });
81
+
82
+ it('D1, D2, D3 are info-tier topology/convention decisions with no runtime gate', () => {
83
+ for (const code of ['D1', 'D2', 'D3'] as const) {
84
+ const entry = getInvariant(`ADR-056.${code}`);
85
+ expect(entry).toBeDefined();
86
+ expect(entry?.severity).toBe('info');
87
+ expect(entry?.runtimeGate).toBeNull();
88
+ }
89
+ });
90
+
91
+ it('every severity:"error" ADR-056 entry has a non-null runtimeGate', () => {
92
+ const errorTier = ADR_056_INVARIANTS.filter((e) => e.severity === 'error');
93
+ expect(errorTier.length).toBeGreaterThan(0);
94
+ for (const entry of errorTier) {
95
+ expect(entry.runtimeGate).not.toBeNull();
96
+ if (entry.runtimeGate !== null) {
97
+ expect(entry.runtimeGate.module.length).toBeGreaterThan(0);
98
+ expect(entry.runtimeGate.functionName.length).toBeGreaterThan(0);
99
+ }
100
+ }
101
+ });
102
+ });
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Unit tests for the ADR-070 ORC-### orchestration invariants (T10336).
3
+ *
4
+ * The ORC codes are the Orchestrator / Lead / Worker behavioural contract
5
+ * surfaced through the central INVARIANTS_REGISTRY. The tests below assert:
6
+ *
7
+ * 1. At least 10 ORC-### entries are registered (we ship 14).
8
+ * 2. ORC-012 (the only ORC rule with a hard-enforced dispatch gate today)
9
+ * points at the `enforceThinAgent` function in
10
+ * `packages/core/src/orchestration/thin-agent.ts`.
11
+ * 3. Every `severity:'warning'` entry with `runtimeGate: null` carries
12
+ * explanatory `description` text — the gap MUST be documented, not
13
+ * silent.
14
+ * 4. ORC keys are emitted under the `ADR-070.` prefix in declaration
15
+ * order and are merged into the central registry.
16
+ *
17
+ * @epic T10327 — E-INVARIANT-REGISTRY-SSOT
18
+ * @saga T10326 — SG-SUBSTRATE-RECONCILIATION
19
+ * @task T10336 — R2: enumerate ADR-070 ORC codes + register
20
+ */
21
+
22
+ import { describe, expect, it } from 'vitest';
23
+ import {
24
+ ADR_070_INVARIANT_COUNT,
25
+ ADR_070_INVARIANTS,
26
+ ADR_070_MIN_ENTRIES,
27
+ } from '../adr-070-orchestration.js';
28
+ import {
29
+ getInvariant,
30
+ getInvariantsByAdr,
31
+ INVARIANTS_REGISTRY,
32
+ type RegisteredInvariant,
33
+ } from '../index.js';
34
+
35
+ /**
36
+ * Minimum ORC-### entry count required by AC1 — "Inventory ~ORC-001
37
+ * through ~ORC-020". We ship 14 today; the floor is 10 so future
38
+ * removals trip the gate before silently dropping coverage.
39
+ */
40
+ const MIN_ORC_ENTRIES = 10;
41
+
42
+ /**
43
+ * The ORC entry that has a hard-enforced dispatch-time guard today.
44
+ * Other entries either prompt-time (skill text) or filed-but-unshipped
45
+ * (ORC-010 / ORC-011 → T10278 / T10279).
46
+ */
47
+ const HARD_ENFORCED_ORC_CODE = 'ORC-012';
48
+
49
+ /**
50
+ * Expected runtime-gate module + function for ORC-012. Asserting both
51
+ * fields prevents silent drift if the thin-agent module is renamed or
52
+ * the export is shadowed.
53
+ */
54
+ const ORC_012_RUNTIME_GATE = {
55
+ module: 'packages/core/src/orchestration/thin-agent.ts',
56
+ functionName: 'enforceThinAgent',
57
+ } as const;
58
+
59
+ describe('ADR_070_INVARIANTS — enumeration shape', () => {
60
+ it('exposes at least 10 ORC-### entries', () => {
61
+ expect(ADR_070_INVARIANTS.length).toBeGreaterThanOrEqual(MIN_ORC_ENTRIES);
62
+ });
63
+
64
+ it('keeps ADR_070_INVARIANT_COUNT in sync with the array length', () => {
65
+ expect(ADR_070_INVARIANT_COUNT).toBe(ADR_070_INVARIANTS.length);
66
+ });
67
+
68
+ it('declares ADR_070_MIN_ENTRIES at or above the AC1 floor', () => {
69
+ expect(ADR_070_MIN_ENTRIES).toBeGreaterThanOrEqual(MIN_ORC_ENTRIES);
70
+ expect(ADR_070_MIN_ENTRIES).toBeLessThanOrEqual(ADR_070_INVARIANTS.length);
71
+ });
72
+
73
+ it('every entry sets adr="ADR-070" and a non-empty ORC-#### code', () => {
74
+ for (const entry of ADR_070_INVARIANTS) {
75
+ expect(entry.adr).toBe('ADR-070');
76
+ expect(entry.code).toMatch(/^ORC-\d{3}$/);
77
+ }
78
+ });
79
+
80
+ it('every entry carries a non-empty name and description', () => {
81
+ for (const entry of ADR_070_INVARIANTS) {
82
+ expect(entry.name.length).toBeGreaterThan(0);
83
+ expect(entry.description.length).toBeGreaterThan(0);
84
+ }
85
+ });
86
+
87
+ it('codes are unique across the ADR-070 enumeration', () => {
88
+ const codes = ADR_070_INVARIANTS.map((e) => e.code);
89
+ const uniqueCodes = new Set(codes);
90
+ expect(uniqueCodes.size).toBe(codes.length);
91
+ });
92
+ });
93
+
94
+ describe('ADR-070 ORC-012 — thin-agent dispatch gate (hard-enforced)', () => {
95
+ it('points at enforceThinAgent in packages/core/src/orchestration/thin-agent.ts', () => {
96
+ const entry = ADR_070_INVARIANTS.find((e) => e.code === HARD_ENFORCED_ORC_CODE);
97
+ expect(entry).toBeDefined();
98
+ expect(entry?.severity).toBe('error');
99
+ expect(entry?.runtimeGate).not.toBeNull();
100
+ expect(entry?.runtimeGate?.module).toBe(ORC_012_RUNTIME_GATE.module);
101
+ expect(entry?.runtimeGate?.functionName).toBe(ORC_012_RUNTIME_GATE.functionName);
102
+ });
103
+
104
+ it('is reachable via INVARIANTS_REGISTRY["ADR-070.ORC-012"]', () => {
105
+ const fromRegistry = INVARIANTS_REGISTRY['ADR-070.ORC-012'];
106
+ expect(fromRegistry).toBeDefined();
107
+ expect(fromRegistry?.runtimeGate?.functionName).toBe(ORC_012_RUNTIME_GATE.functionName);
108
+ });
109
+ });
110
+
111
+ describe('ADR-070 gap documentation — warning + runtimeGate:null entries', () => {
112
+ it('every warning entry with runtimeGate:null carries explanatory description text', () => {
113
+ const gaps: RegisteredInvariant[] = ADR_070_INVARIANTS.filter(
114
+ (e) => e.severity === 'warning' && e.runtimeGate === null,
115
+ );
116
+
117
+ // We expect at least the prompt-only ORC entries (ORC-001..003, 008, 009)
118
+ // plus the filed-but-unshipped depth/lead entries (ORC-010 / ORC-011).
119
+ expect(gaps.length).toBeGreaterThanOrEqual(5);
120
+
121
+ // Each gap MUST explain WHY no runtime gate exists. We probe for the
122
+ // documented surface markers ("UNENFORCED", "Prompt-time",
123
+ // "FILED but UNSHIPPED" — and the related verb forms). At least one of
124
+ // these phrases is required so the R6 doctor audit can rely on the
125
+ // description as a human-readable gap explanation.
126
+ const gapMarkers = ['UNENFORCED', 'prompt-time', 'Prompt-time', 'UNSHIPPED', 'unshipped'];
127
+ for (const entry of gaps) {
128
+ const hasMarker = gapMarkers.some((m) => entry.description.includes(m));
129
+ expect(hasMarker, `Entry ${entry.code} description is missing a gap marker`).toBe(true);
130
+ // Sanity check: description is substantive, not a stub.
131
+ expect(entry.description.length).toBeGreaterThanOrEqual(80);
132
+ }
133
+ });
134
+
135
+ it('filed-but-unshipped ORC-010 / ORC-011 reference their tracking tasks', () => {
136
+ const orc010 = ADR_070_INVARIANTS.find((e) => e.code === 'ORC-010');
137
+ const orc011 = ADR_070_INVARIANTS.find((e) => e.code === 'ORC-011');
138
+ expect(orc010?.description).toContain('T10278');
139
+ expect(orc011?.description).toContain('T10279');
140
+ });
141
+ });
142
+
143
+ describe('central registry — ADR-070 entries merged via index.ts', () => {
144
+ it('every ADR-070 entry is reachable via getInvariant("ADR-070.<code>")', () => {
145
+ for (const entry of ADR_070_INVARIANTS) {
146
+ const key = `ADR-070.${entry.code}`;
147
+ const fromRegistry = getInvariant(key);
148
+ expect(fromRegistry, `Missing registry entry for ${key}`).toBeDefined();
149
+ expect(fromRegistry?.code).toBe(entry.code);
150
+ }
151
+ });
152
+
153
+ it('getInvariantsByAdr("ADR-070") returns every entry in declaration order', () => {
154
+ const adr070 = getInvariantsByAdr('ADR-070');
155
+ expect(adr070).toHaveLength(ADR_070_INVARIANTS.length);
156
+ for (let i = 0; i < ADR_070_INVARIANTS.length; i++) {
157
+ expect(adr070[i]?.code).toBe(ADR_070_INVARIANTS[i]?.code);
158
+ }
159
+ });
160
+
161
+ it('does not collide with ADR-073 entries under the merged key space', () => {
162
+ const adr070Keys = ADR_070_INVARIANTS.map((e) => `ADR-070.${e.code}`);
163
+ const adr073Keys = getInvariantsByAdr('ADR-073').map((e) => `ADR-073.${e.code}`);
164
+ const intersection = adr070Keys.filter((k) => adr073Keys.includes(k));
165
+ expect(intersection).toEqual([]);
166
+ });
167
+ });
168
+
169
+ describe('ADR-070 severity:"error" entries — runtime gate invariant', () => {
170
+ it('every error-tier entry has a non-null runtimeGate (R4 T10338 strict gate)', () => {
171
+ const errorTier: RegisteredInvariant[] = ADR_070_INVARIANTS.filter(
172
+ (e) => e.severity === 'error',
173
+ );
174
+ expect(errorTier.length).toBeGreaterThan(0);
175
+ for (const entry of errorTier) {
176
+ expect(
177
+ entry.runtimeGate,
178
+ `Error-tier entry ${entry.code} missing runtimeGate`,
179
+ ).not.toBeNull();
180
+ if (entry.runtimeGate !== null) {
181
+ expect(entry.runtimeGate.module.length).toBeGreaterThan(0);
182
+ expect(entry.runtimeGate.functionName.length).toBeGreaterThan(0);
183
+ }
184
+ }
185
+ });
186
+ });
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Unit tests for the central invariants registry (T10335).
3
+ *
4
+ * The registry is the SSoT for system-wide invariants surfaced via the
5
+ * SG-SUBSTRATE-RECONCILIATION saga. Downstream R-tasks (R2 ORC codes,
6
+ * R4 CI gate, R5 release refactor, R6 doctor audit, R8 docs renderer)
7
+ * all key off the metadata shape asserted below.
8
+ *
9
+ * @epic T10327 — E-INVARIANT-REGISTRY-SSOT
10
+ * @saga T10326 — SG-SUBSTRATE-RECONCILIATION
11
+ * @task T10335 — R1: registry + ADR-073 I1-I8
12
+ */
13
+
14
+ import { describe, expect, it } from 'vitest';
15
+ import {
16
+ E_SAGA_INVARIANT_VIOLATION_I3,
17
+ E_SAGA_INVARIANT_VIOLATION_I5,
18
+ E_SAGA_INVARIANT_VIOLATION_I7,
19
+ } from '../../errors.js';
20
+ import {
21
+ ADR_073_INVARIANTS,
22
+ getInvariant,
23
+ getInvariantsByAdr,
24
+ INVARIANTS_REGISTRY,
25
+ type RegisteredInvariant,
26
+ } from '../index.js';
27
+
28
+ /**
29
+ * Map from ADR-073 invariant code → expected LAFS error code in
30
+ * `packages/contracts/src/errors.ts`. R2/R4 will extend this mapping with
31
+ * ADR-070 ORC codes once they land — for now the registry only knows
32
+ * about the three saga-runtime guards.
33
+ *
34
+ * NOTE: I4 / I6 / I8 deliberately have no error code today — they are
35
+ * UNENFORCED warnings. The R4 CI gate (T10338) is the right home for the
36
+ * strict "every error has an error code" assertion; this test only
37
+ * validates the subset we already have wired.
38
+ */
39
+ const ADR_073_ERROR_CODE_MAP: Record<string, string> = {
40
+ I3: E_SAGA_INVARIANT_VIOLATION_I3,
41
+ I5: E_SAGA_INVARIANT_VIOLATION_I5,
42
+ I7: E_SAGA_INVARIANT_VIOLATION_I7,
43
+ };
44
+
45
+ describe('INVARIANTS_REGISTRY — central registry shape', () => {
46
+ it('exposes at least 8 entries (ADR-073 I1-I8 baseline)', () => {
47
+ const entries = Object.values(INVARIANTS_REGISTRY);
48
+ expect(entries.length).toBeGreaterThanOrEqual(8);
49
+ });
50
+
51
+ it('registers all eight ADR-073 invariants under predictable keys', () => {
52
+ for (let i = 1; i <= 8; i++) {
53
+ const key = `ADR-073.I${i}`;
54
+ expect(INVARIANTS_REGISTRY[key]).toBeDefined();
55
+ expect(INVARIANTS_REGISTRY[key]?.adr).toBe('ADR-073');
56
+ expect(INVARIANTS_REGISTRY[key]?.code).toBe(`I${i}`);
57
+ }
58
+ });
59
+
60
+ it('every entry carries a non-empty name and description', () => {
61
+ for (const entry of Object.values(INVARIANTS_REGISTRY)) {
62
+ expect(entry.name.length).toBeGreaterThan(0);
63
+ expect(entry.description.length).toBeGreaterThan(0);
64
+ }
65
+ });
66
+
67
+ it('every severity:"error" invariant has a non-null runtimeGate', () => {
68
+ const errorTier: RegisteredInvariant[] = Object.values(INVARIANTS_REGISTRY).filter(
69
+ (e) => e.severity === 'error',
70
+ );
71
+ expect(errorTier.length).toBeGreaterThan(0);
72
+ for (const entry of errorTier) {
73
+ expect(entry.runtimeGate).not.toBeNull();
74
+ // Triple is fully populated when present.
75
+ if (entry.runtimeGate !== null) {
76
+ expect(entry.runtimeGate.module.length).toBeGreaterThan(0);
77
+ expect(entry.runtimeGate.functionName.length).toBeGreaterThan(0);
78
+ }
79
+ }
80
+ });
81
+ });
82
+
83
+ describe('getInvariant — lookup helper', () => {
84
+ it('returns the ADR-073.I3 entry with the expected runtime guard', () => {
85
+ const i3 = getInvariant('ADR-073.I3');
86
+ expect(i3).toBeDefined();
87
+ expect(i3?.adr).toBe('ADR-073');
88
+ expect(i3?.code).toBe('I3');
89
+ expect(i3?.severity).toBe('error');
90
+ expect(i3?.runtimeGate?.functionName).toBe('assertSagaInvariantI3');
91
+ expect(i3?.runtimeGate?.module).toBe('packages/core/src/sagas/enforcement.ts');
92
+ });
93
+
94
+ it('returns undefined for an unregistered key', () => {
95
+ expect(getInvariant('ADR-999.X1')).toBeUndefined();
96
+ });
97
+ });
98
+
99
+ describe('getInvariantsByAdr — per-ADR enumeration', () => {
100
+ it('returns all eight ADR-073 invariants in declaration order', () => {
101
+ const adr073 = getInvariantsByAdr('ADR-073');
102
+ expect(adr073).toHaveLength(8);
103
+ expect(adr073.map((e) => e.code)).toEqual(['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8']);
104
+ });
105
+
106
+ it('returns an empty array for an unknown ADR', () => {
107
+ expect(getInvariantsByAdr('ADR-DOES-NOT-EXIST')).toEqual([]);
108
+ });
109
+ });
110
+
111
+ describe('ADR_073_INVARIANTS — exported convenience binding', () => {
112
+ it('matches the entries surfaced via getInvariantsByAdr', () => {
113
+ expect(ADR_073_INVARIANTS).toHaveLength(8);
114
+ const enumerated = getInvariantsByAdr('ADR-073');
115
+ expect(enumerated).toHaveLength(ADR_073_INVARIANTS.length);
116
+ for (let i = 0; i < ADR_073_INVARIANTS.length; i++) {
117
+ expect(enumerated[i]?.code).toBe(ADR_073_INVARIANTS[i]?.code);
118
+ }
119
+ });
120
+ });
121
+
122
+ describe('error-code cross-reference (subset — R4 T10338 owns full strict gate)', () => {
123
+ it('every ADR-073 invariant with a runtimeGate has a known LAFS error code', () => {
124
+ // TODO(T10338 / R4): replace this targeted subset with a strict registry-wide
125
+ // assertion that walks every severity:"error" entry and confirms a matching
126
+ // LAFS error code (E_<ADR>_INVARIANT_VIOLATION_<CODE>) exists in errors.ts.
127
+ const adr073 = getInvariantsByAdr('ADR-073');
128
+ for (const entry of adr073) {
129
+ if (entry.runtimeGate !== null) {
130
+ const expected = ADR_073_ERROR_CODE_MAP[entry.code];
131
+ expect(expected).toBeDefined();
132
+ expect(typeof expected).toBe('string');
133
+ }
134
+ }
135
+ });
136
+ });