@open-agent-toolkit/cli 0.2.72 → 0.2.74

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 (165) hide show
  1. package/assets/agents/oat-reviewer.md +4 -3
  2. package/assets/agents/recon-worker.md +42 -8
  3. package/assets/bundle-metadata.json +1 -1
  4. package/assets/docs/cli-utilities/configuration.md +3 -56
  5. package/assets/docs/cli-utilities/tool-packs.md +4 -4
  6. package/assets/docs/contributing/code.md +2 -2
  7. package/assets/docs/contributing/index.md +0 -1
  8. package/assets/docs/reference/cli-reference.md +1 -1
  9. package/assets/docs/reference/troubleshooting.md +17 -40
  10. package/assets/docs/workflows/projects/artifacts.md +16 -34
  11. package/assets/docs/workflows/projects/lifecycle.md +20 -19
  12. package/assets/docs/workflows/skills/explainer-kit.md +180 -440
  13. package/assets/docs/workflows/skills/index.md +1 -2
  14. package/assets/docs/workflows/skills/recon.md +114 -23
  15. package/assets/public-package-versions.json +4 -4
  16. package/assets/skills/explainer-kit/SKILL.md +96 -171
  17. package/assets/skills/explainer-kit/recipes/engineer-tour.json +1 -22
  18. package/assets/skills/explainer-kit/recipes/program-recap.json +1 -29
  19. package/assets/skills/explainer-kit/recipes/project-explainer.json +1 -22
  20. package/assets/skills/explainer-kit/recipes/project-recap.v2.json +1 -43
  21. package/assets/skills/explainer-kit/references/fact-base-contract.md +76 -101
  22. package/assets/skills/explainer-kit/references/recap-authoring.md +99 -0
  23. package/assets/skills/explainer-kit/schemas/manifest.schema.json +13 -135
  24. package/assets/skills/explainer-kit/scripts/bundle.mjs +712 -0
  25. package/assets/skills/explainer-kit/scripts/lib/claim-subject.mjs +40 -0
  26. package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +113 -1504
  27. package/assets/skills/explainer-kit/scripts/lib/failure.mjs +13 -0
  28. package/assets/skills/explainer-kit/scripts/lib/package-coverage.mjs +57 -403
  29. package/assets/skills/explainer-kit/scripts/lib/qa-result.mjs +168 -0
  30. package/assets/skills/explainer-kit/scripts/lib/qa.mjs +90 -488
  31. package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +28 -545
  32. package/assets/skills/explainer-kit/scripts/lib/run-package.mjs +120 -0
  33. package/assets/skills/explainer-kit/scripts/lib/sanitize.mjs +42 -0
  34. package/assets/skills/explainer-kit/scripts/record.mjs +335 -0
  35. package/assets/skills/explainer-kit/scripts/verify.mjs +648 -0
  36. package/assets/skills/oat-explainer-kit/SKILL.md +57 -117
  37. package/assets/skills/oat-explainer-kit/references/config-contract.md +20 -64
  38. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +85 -237
  39. package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +18 -10
  40. package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +1 -0
  41. package/assets/skills/oat-explainer-kit/scripts/check-terminal-outcome.mjs +256 -21
  42. package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +82 -3
  43. package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +0 -162
  44. package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +57 -123
  45. package/assets/skills/oat-project-autonomous/SKILL.md +14 -13
  46. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
  47. package/assets/skills/oat-project-complete/SKILL.md +168 -349
  48. package/assets/skills/oat-project-complete/scripts/consume-persisted-recap-intent.mjs +125 -0
  49. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +0 -30
  50. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +4 -37
  51. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +17 -16
  52. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +9 -174
  53. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +1 -2
  54. package/assets/skills/oat-project-document/SKILL.md +1 -1
  55. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
  56. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  57. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +44 -56
  58. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
  59. package/assets/skills/oat-project-lite/SKILL.md +1 -1
  60. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +165 -165
  61. package/assets/skills/oat-project-plan/SKILL.md +5 -6
  62. package/assets/skills/oat-project-pr-final/SKILL.md +1 -1
  63. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
  64. package/assets/skills/oat-project-quick-start/SKILL.md +1 -1
  65. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
  66. package/assets/skills/oat-project-review-receive/SKILL.md +14 -1
  67. package/assets/skills/oat-project-summary/SKILL.md +35 -25
  68. package/assets/skills/oat-wave-execute/SKILL.md +11 -36
  69. package/assets/skills/oat-wave-program/SKILL.md +13 -37
  70. package/assets/skills/recon/SKILL.md +103 -40
  71. package/assets/skills/recon/references/packet-contract.md +150 -67
  72. package/assets/skills/recon/references/profiles.md +38 -16
  73. package/assets/skills/recon/references/worker-contract.md +39 -8
  74. package/assets/skills/recon/scripts/lib/contracts.mjs +703 -55
  75. package/assets/skills/recon/scripts/lib/routing.mjs +588 -0
  76. package/assets/skills/recon/scripts/lib/validated-run.mjs +2 -0
  77. package/assets/skills/recon/scripts/prepare-routing.mjs +88 -0
  78. package/assets/skills/recon/scripts/render-packet.mjs +69 -1
  79. package/assets/skills/recon/scripts/validate-packet.mjs +473 -56
  80. package/assets/skills/subagent-orchestration/SKILL.md +18 -9
  81. package/assets/skills/subagent-orchestration/references/model-selection-principles.md +17 -8
  82. package/dist/commands/config/index.d.ts.map +1 -1
  83. package/dist/commands/config/index.js +9 -113
  84. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  85. package/dist/commands/project/archive/archive-utils.js +32 -184
  86. package/dist/commands/project/archive/explainer-package-coverage.d.ts +3 -8
  87. package/dist/commands/project/archive/explainer-package-coverage.d.ts.map +1 -1
  88. package/dist/commands/project/archive/explainer-package-coverage.js +1 -1
  89. package/dist/config/oat-config.d.ts +0 -11
  90. package/dist/config/oat-config.d.ts.map +1 -1
  91. package/dist/config/oat-config.js +0 -34
  92. package/dist/config/resolve.d.ts.map +1 -1
  93. package/dist/config/resolve.js +0 -8
  94. package/dist/release/public-package-contract.d.ts.map +1 -1
  95. package/dist/release/public-package-contract.js +2 -10
  96. package/dist/validation/project-state.d.ts.map +1 -1
  97. package/dist/validation/project-state.js +42 -3
  98. package/package.json +2 -2
  99. package/assets/docs/contributing/explainer-kit-verification.md +0 -133
  100. package/assets/docs/workflows/skills/explainer-kit-providers.md +0 -155
  101. package/assets/skills/explainer-kit/briefs/deep-dive.md +0 -35
  102. package/assets/skills/explainer-kit/briefs/project-page.md +0 -38
  103. package/assets/skills/explainer-kit/briefs/supporting-diagram.md +0 -28
  104. package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +0 -35
  105. package/assets/skills/explainer-kit/examples/project-explainer/content.md +0 -31
  106. package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +0 -22
  107. package/assets/skills/explainer-kit/examples/project-recap/content.md +0 -77
  108. package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +0 -104
  109. package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +0 -44
  110. package/assets/skills/explainer-kit/examples/theme-bundle.json +0 -87
  111. package/assets/skills/explainer-kit/recipes/project-recap.v1.json +0 -93
  112. package/assets/skills/explainer-kit/references/contracts.md +0 -324
  113. package/assets/skills/explainer-kit/references/destination-contract.md +0 -210
  114. package/assets/skills/explainer-kit/references/extension-contract.md +0 -80
  115. package/assets/skills/explainer-kit/references/golden-conformance.md +0 -80
  116. package/assets/skills/explainer-kit/references/visual-review.md +0 -71
  117. package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +0 -212
  118. package/assets/skills/explainer-kit/schemas/author-request.v3.schema.json +0 -241
  119. package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +0 -52
  120. package/assets/skills/explainer-kit/schemas/build-record.schema.json +0 -98
  121. package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +0 -53
  122. package/assets/skills/explainer-kit/schemas/publish-receipt.v1.schema.json +0 -97
  123. package/assets/skills/explainer-kit/schemas/publish-receipt.v2.schema.json +0 -215
  124. package/assets/skills/explainer-kit/schemas/publish-request.v1.schema.json +0 -32
  125. package/assets/skills/explainer-kit/schemas/publish-request.v2.schema.json +0 -34
  126. package/assets/skills/explainer-kit/schemas/run-request.schema.json +0 -147
  127. package/assets/skills/explainer-kit/schemas/set-plan.v1.schema.json +0 -149
  128. package/assets/skills/explainer-kit/schemas/terminal-evidence.v1.schema.json +0 -81
  129. package/assets/skills/explainer-kit/schemas/visual-review-evidence.v1.schema.json +0 -66
  130. package/assets/skills/explainer-kit/schemas/visual-review-request.v1.schema.json +0 -117
  131. package/assets/skills/explainer-kit/schemas/visual-review-result.v1.schema.json +0 -80
  132. package/assets/skills/explainer-kit/scripts/lib/catalog.mjs +0 -349
  133. package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +0 -418
  134. package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +0 -514
  135. package/assets/skills/explainer-kit/scripts/lib/durability.mjs +0 -643
  136. package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +0 -664
  137. package/assets/skills/explainer-kit/scripts/lib/internal-references.mjs +0 -538
  138. package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +0 -414
  139. package/assets/skills/explainer-kit/scripts/lib/publication-policy.mjs +0 -54
  140. package/assets/skills/explainer-kit/scripts/lib/records.mjs +0 -954
  141. package/assets/skills/explainer-kit/scripts/lib/render.mjs +0 -703
  142. package/assets/skills/explainer-kit/scripts/lib/s3-roots.mjs +0 -353
  143. package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +0 -759
  144. package/assets/skills/explainer-kit/scripts/lib/set-plan.mjs +0 -209
  145. package/assets/skills/explainer-kit/scripts/lib/source-backlinks.mjs +0 -218
  146. package/assets/skills/explainer-kit/scripts/lib/terminal-evidence.mjs +0 -157
  147. package/assets/skills/explainer-kit/scripts/lib/visual-review.mjs +0 -400
  148. package/assets/skills/explainer-kit/scripts/publish.mjs +0 -76
  149. package/assets/skills/explainer-kit/scripts/record-durability.mjs +0 -64
  150. package/assets/skills/explainer-kit/scripts/render-qa.mjs +0 -461
  151. package/assets/skills/explainer-kit/scripts/run.mjs +0 -3038
  152. package/assets/skills/explainer-kit/scripts/validate.mjs +0 -59
  153. package/assets/skills/oat-explainer-kit/references/author-callback.md +0 -53
  154. package/assets/skills/oat-explainer-kit/references/migration.md +0 -144
  155. package/assets/skills/oat-explainer-kit/references/visual-review-callback.md +0 -83
  156. package/assets/skills/oat-explainer-kit/scripts/derive-destination.mjs +0 -91
  157. package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +0 -531
  158. package/assets/skills/oat-explainer-kit/scripts/probe-recap-seams.mjs +0 -371
  159. package/assets/skills/oat-explainer-kit/scripts/run.mjs +0 -905
  160. package/dist/commands/project/archive/explainer-source-backlinks.d.ts +0 -18
  161. package/dist/commands/project/archive/explainer-source-backlinks.d.ts.map +0 -1
  162. package/dist/commands/project/archive/explainer-source-backlinks.js +0 -27
  163. package/dist/commands/project/archive/explainer-terminal-evidence.d.ts +0 -29
  164. package/dist/commands/project/archive/explainer-terminal-evidence.d.ts.map +0 -1
  165. package/dist/commands/project/archive/explainer-terminal-evidence.js +0 -37
@@ -1,4 +1,5 @@
1
1
  export const SCHEMA_VERSION = 1;
2
+ export const MANIFEST_SCHEMA_VERSION = 2;
2
3
 
3
4
  export const artifactKinds = new Set([
4
5
  'recon.packet-manifest',
@@ -61,6 +62,110 @@ export const taskClasses = [
61
62
  'consequential',
62
63
  ];
63
64
 
65
+ export const conditionPredicates = [
66
+ 'insufficient-evidence',
67
+ 'unresolved-material-challenge',
68
+ ];
69
+
70
+ function profilePolicy(
71
+ orderedSingletonWaveModes,
72
+ allowedWaveModes,
73
+ countedLaneModes,
74
+ caps,
75
+ ) {
76
+ return Object.freeze({
77
+ orderedSingletonWaveModes: Object.freeze(orderedSingletonWaveModes),
78
+ allowedWaveModes: Object.freeze(allowedWaveModes),
79
+ countedLaneModes: Object.freeze(countedLaneModes),
80
+ ...caps,
81
+ });
82
+ }
83
+
84
+ export const profileRoutingPolicy = Object.freeze({
85
+ quick: profilePolicy(
86
+ ['map', 'gather', 'compile'],
87
+ ['map', 'gather', 'compile'],
88
+ ['gather'],
89
+ {
90
+ lanes: 4,
91
+ concurrency: 4,
92
+ conditions: 0,
93
+ },
94
+ ),
95
+ standard: profilePolicy(
96
+ [
97
+ 'map',
98
+ 'gather',
99
+ 'compile',
100
+ 'semantic-verification',
101
+ 'adversarial',
102
+ 'coverage',
103
+ 'reconciliation',
104
+ ],
105
+ [
106
+ 'map',
107
+ 'gather',
108
+ 'compile',
109
+ 'semantic-verification',
110
+ 'adversarial',
111
+ 'coverage',
112
+ 'reconciliation',
113
+ 'contradiction-resolution',
114
+ ],
115
+ [
116
+ 'gather',
117
+ 'semantic-verification',
118
+ 'adversarial',
119
+ 'coverage',
120
+ 'contradiction-resolution',
121
+ ],
122
+ { lanes: 10, concurrency: 6, conditions: 1 },
123
+ ),
124
+ thorough: profilePolicy(
125
+ [
126
+ 'map',
127
+ 'gather',
128
+ 'redundant-gather',
129
+ 'compile',
130
+ 'semantic-verification',
131
+ 'adversarial',
132
+ 'coverage',
133
+ 'redundant-verification',
134
+ 'reconciliation',
135
+ ],
136
+ [
137
+ 'map',
138
+ 'gather',
139
+ 'compile',
140
+ 'semantic-verification',
141
+ 'adversarial',
142
+ 'coverage',
143
+ 'redundant-gather',
144
+ 'redundant-verification',
145
+ 'reconciliation',
146
+ 'contradiction-resolution',
147
+ ],
148
+ [
149
+ 'gather',
150
+ 'semantic-verification',
151
+ 'adversarial',
152
+ 'coverage',
153
+ 'redundant-gather',
154
+ 'redundant-verification',
155
+ 'contradiction-resolution',
156
+ ],
157
+ { lanes: 20, concurrency: 8, conditions: 2 },
158
+ ),
159
+ });
160
+
161
+ const supportedSchemaVersions = new Map([
162
+ ['recon.packet-manifest', new Set([MANIFEST_SCHEMA_VERSION])],
163
+ ['recon.claim-ledger', new Set([SCHEMA_VERSION])],
164
+ ['recon.raw-dossier', new Set([SCHEMA_VERSION])],
165
+ ['recon.review-brief', new Set([SCHEMA_VERSION])],
166
+ ['recon.review-result', new Set([SCHEMA_VERSION])],
167
+ ]);
168
+
64
169
  const legalTransitions = new Set([
65
170
  'provisional:supported',
66
171
  'provisional:verified',
@@ -192,6 +297,22 @@ function requiredObject(value, key, errors, path = '$') {
192
297
  }
193
298
  }
194
299
 
300
+ function requiredNullableString(value, key, errors, path = '$') {
301
+ if (
302
+ !Object.hasOwn(value ?? {}, key) ||
303
+ (value[key] !== null &&
304
+ (typeof value[key] !== 'string' || value[key].length === 0))
305
+ ) {
306
+ errors.push(
307
+ issue(
308
+ 'MISSING_REQUIRED_FIELD',
309
+ `${key} must be a non-empty string or null`,
310
+ `${path}.${key}`,
311
+ ),
312
+ );
313
+ }
314
+ }
315
+
195
316
  function closedObject(value, allowed, errors, path = '$') {
196
317
  if (!isObject(value)) return;
197
318
  for (const key of Object.keys(value)) {
@@ -220,7 +341,7 @@ function duplicateIds(values, path, errors) {
220
341
  }
221
342
  }
222
343
 
223
- const executionKeys = new Set([
344
+ const exactTargetKeys = new Set([
224
345
  'provider',
225
346
  'route',
226
347
  'role',
@@ -228,27 +349,22 @@ const executionKeys = new Set([
228
349
  'effort',
229
350
  'reasoningMode',
230
351
  'serviceTier',
352
+ ]);
353
+
354
+ const executionV2Keys = new Set([
355
+ 'target',
231
356
  'authority',
232
357
  'maxConcurrency',
233
358
  'deadlineSeconds',
234
359
  'retryLimit',
235
360
  'waves',
361
+ 'conditions',
236
362
  'approval',
237
363
  ]);
238
364
 
239
- export function approvalFingerprintInput(execution) {
240
- const { approval: _approval, ...approved } = execution;
241
- return approved;
242
- }
243
-
244
365
  function validateApprovalEvidence(value, errors, path) {
245
366
  if (!isObject(value)) return;
246
- closedObject(
247
- value,
248
- new Set(['type', 'approvedAt', 'fingerprint']),
249
- errors,
250
- path,
251
- );
367
+ closedObject(value, new Set(['type', 'approvedAt']), errors, path);
252
368
  if (value.type !== 'explicit-user-approval') {
253
369
  errors.push(
254
370
  issue(
@@ -259,34 +375,24 @@ function validateApprovalEvidence(value, errors, path) {
259
375
  );
260
376
  }
261
377
  requiredTimestamp(value, 'approvedAt', errors, path);
262
- if (!isDigest(value.fingerprint)) {
263
- errors.push(
264
- issue(
265
- 'INVALID_APPROVAL_FINGERPRINT',
266
- 'Approval evidence fingerprint must be sha256',
267
- `${path}.fingerprint`,
268
- ),
269
- );
270
- }
271
378
  }
272
379
 
273
- export function validateExecution(value, errors, path = '$.execution') {
380
+ function validateExactTarget(value, errors, path) {
274
381
  if (!isObject(value)) return;
275
- closedObject(value, executionKeys, errors, path);
276
- for (const key of ['provider', 'route', 'role', 'model', 'effort']) {
382
+ closedObject(value, exactTargetKeys, errors, path);
383
+ for (const key of ['provider', 'route', 'role', 'model']) {
277
384
  requiredString(value, key, errors, path);
278
385
  }
279
- for (const key of ['reasoningMode', 'serviceTier']) {
280
- if (value[key] !== null && typeof value[key] !== 'string') {
281
- errors.push(
282
- issue(
283
- 'MISSING_REQUIRED_FIELD',
284
- `${key} must be a string or null`,
285
- `${path}.${key}`,
286
- ),
287
- );
288
- }
386
+ for (const key of ['effort', 'reasoningMode', 'serviceTier']) {
387
+ requiredNullableString(value, key, errors, path);
289
388
  }
389
+ }
390
+
391
+ function validateExecutionV2(value, errors, path) {
392
+ if (!isObject(value)) return;
393
+ closedObject(value, executionV2Keys, errors, path);
394
+ requiredObject(value, 'target', errors, path);
395
+ validateExactTarget(value.target, errors, `${path}.target`);
290
396
  if (!authorityLevels.includes(value.authority)) {
291
397
  errors.push(
292
398
  issue(
@@ -300,20 +406,47 @@ export function validateExecution(value, errors, path = '$.execution') {
300
406
  requiredInteger(value, 'deadlineSeconds', errors, path, 1);
301
407
  requiredInteger(value, 'retryLimit', errors, path, 0);
302
408
  requiredArray(value, 'waves', errors, path);
409
+ requiredArray(value, 'conditions', errors, path);
303
410
  requiredObject(value, 'approval', errors, path);
304
411
  validateApprovalEvidence(value.approval, errors, `${path}.approval`);
412
+
413
+ const waves = Array.isArray(value.waves) ? value.waves : [];
305
414
  const waveIds = new Set();
306
415
  const laneIds = new Set();
307
- for (const [waveIndex, wave] of (value.waves ?? []).entries()) {
416
+ const writeRoots = new Set();
417
+ for (const [waveIndex, wave] of waves.entries()) {
308
418
  const wavePath = `${path}.waves[${waveIndex}]`;
309
419
  closedObject(
310
420
  wave,
311
- new Set(['waveId', 'mode', 'taskClass', 'lanes', 'conditional']),
421
+ new Set([
422
+ 'waveId',
423
+ 'mode',
424
+ 'taskClass',
425
+ 'classFloor',
426
+ 'selectionReason',
427
+ 'target',
428
+ 'lanes',
429
+ 'conditional',
430
+ ]),
312
431
  errors,
313
432
  wavePath,
314
433
  );
315
434
  requiredString(wave, 'waveId', errors, wavePath);
435
+ requiredString(wave, 'selectionReason', errors, wavePath);
316
436
  requiredArray(wave, 'lanes', errors, wavePath);
437
+ if (Object.hasOwn(wave ?? {}, 'target')) {
438
+ if (!isObject(wave.target)) {
439
+ errors.push(
440
+ issue(
441
+ 'MISSING_REQUIRED_FIELD',
442
+ 'target must be a complete exact target object',
443
+ `${wavePath}.target`,
444
+ ),
445
+ );
446
+ } else {
447
+ validateExactTarget(wave.target, errors, `${wavePath}.target`);
448
+ }
449
+ }
317
450
  if (Array.isArray(wave?.lanes) && wave.lanes.length === 0) {
318
451
  errors.push(
319
452
  issue(
@@ -328,7 +461,9 @@ export function validateExecution(value, errors, path = '$.execution') {
328
461
  issue('INVALID_WAVE_MODE', 'Unknown wave mode', `${wavePath}.mode`),
329
462
  );
330
463
  }
331
- if (!taskClasses.includes(wave?.taskClass)) {
464
+ const taskClassIndex = taskClasses.indexOf(wave?.taskClass);
465
+ const classFloorIndex = taskClasses.indexOf(wave?.classFloor);
466
+ if (taskClassIndex === -1) {
332
467
  errors.push(
333
468
  issue(
334
469
  'INVALID_TASK_CLASS',
@@ -337,6 +472,23 @@ export function validateExecution(value, errors, path = '$.execution') {
337
472
  ),
338
473
  );
339
474
  }
475
+ if (classFloorIndex === -1) {
476
+ errors.push(
477
+ issue(
478
+ 'INVALID_TASK_CLASS',
479
+ 'Unknown wave class floor',
480
+ `${wavePath}.classFloor`,
481
+ ),
482
+ );
483
+ } else if (taskClassIndex !== -1 && taskClassIndex < classFloorIndex) {
484
+ errors.push(
485
+ issue(
486
+ 'TASK_CLASS_BELOW_FLOOR',
487
+ 'Wave task class must meet or exceed its declared class floor',
488
+ `${wavePath}.taskClass`,
489
+ ),
490
+ );
491
+ }
340
492
  if (typeof wave?.conditional !== 'boolean') {
341
493
  errors.push(
342
494
  issue(
@@ -393,10 +545,425 @@ export function validateExecution(value, errors, path = '$.execution') {
393
545
  );
394
546
  }
395
547
  laneIds.add(lane?.laneId);
548
+ if (
549
+ typeof lane?.writeRoot === 'string' &&
550
+ writeRoots.has(lane.writeRoot)
551
+ ) {
552
+ errors.push(
553
+ issue(
554
+ 'DUPLICATE_WAVE_OUTPUT',
555
+ `Wave output ${lane.writeRoot} is assigned more than once`,
556
+ `${lanePath}.writeRoot`,
557
+ ),
558
+ );
559
+ }
560
+ if (typeof lane?.writeRoot === 'string') writeRoots.add(lane.writeRoot);
396
561
  }
397
562
  }
398
563
  }
399
564
 
565
+ export function validateV2ProfileTopology(
566
+ manifest,
567
+ execution = manifest?.execution,
568
+ path = '$.execution',
569
+ ) {
570
+ const errors = [];
571
+ if (manifest?.schemaVersion !== MANIFEST_SCHEMA_VERSION) return errors;
572
+ const requestedProfile = manifest.run?.requestedProfile;
573
+ const policy = profileRoutingPolicy[requestedProfile];
574
+ if (!policy) {
575
+ return [
576
+ issue(
577
+ 'INVALID_ROUTING_PROFILE',
578
+ 'A v2 routing proposal requires a supported requested profile',
579
+ '$.run.requestedProfile',
580
+ ),
581
+ ];
582
+ }
583
+ if (!isObject(execution)) return errors;
584
+ if (!Array.isArray(execution.waves)) {
585
+ return [
586
+ issue(
587
+ 'MISSING_ROUTING_WAVES',
588
+ 'execution.waves must be an array',
589
+ `${path}.waves`,
590
+ ),
591
+ ];
592
+ }
593
+ if (!Array.isArray(execution.conditions)) {
594
+ return [
595
+ issue(
596
+ 'MISSING_ROUTING_CONDITIONS',
597
+ 'execution.conditions must be an array',
598
+ `${path}.conditions`,
599
+ ),
600
+ ];
601
+ }
602
+ const waves = Array.isArray(execution.waves) ? execution.waves : [];
603
+ const conditions = Array.isArray(execution.conditions)
604
+ ? execution.conditions
605
+ : [];
606
+ const forbiddenModes = waves
607
+ .filter(
608
+ (wave) =>
609
+ typeof wave?.mode === 'string' &&
610
+ !policy.allowedWaveModes.includes(wave.mode),
611
+ )
612
+ .map((wave) => wave.mode);
613
+ if (forbiddenModes.length > 0) {
614
+ errors.push(
615
+ issue(
616
+ 'WAVE_MODE_NOT_ALLOWED_FOR_PROFILE',
617
+ `${requestedProfile} routing does not permit wave modes: ${[
618
+ ...new Set(forbiddenModes),
619
+ ].join(', ')}`,
620
+ `${path}.waves`,
621
+ ),
622
+ );
623
+ }
624
+ for (const [waveIndex, wave] of waves.entries()) {
625
+ if (
626
+ policy.allowedWaveModes.includes(wave?.mode) &&
627
+ !policy.countedLaneModes.includes(wave.mode) &&
628
+ Array.isArray(wave?.lanes) &&
629
+ wave.lanes.length !== 1
630
+ ) {
631
+ errors.push(
632
+ issue(
633
+ 'INVALID_PROFILE_SINGLETON_LANE_COUNT',
634
+ `${requestedProfile} ${wave.mode} waves require exactly one lane`,
635
+ `${path}.waves[${waveIndex}].lanes`,
636
+ ),
637
+ );
638
+ }
639
+ }
640
+ const laneCount = waves.reduce(
641
+ (count, wave) =>
642
+ count +
643
+ (policy.countedLaneModes.includes(wave?.mode) &&
644
+ Array.isArray(wave?.lanes)
645
+ ? wave.lanes.length
646
+ : 0),
647
+ 0,
648
+ );
649
+ if (laneCount > policy.lanes) {
650
+ errors.push(
651
+ issue(
652
+ 'PROFILE_LANE_CAP_EXCEEDED',
653
+ `${requestedProfile} routing permits at most ${policy.lanes} adaptive evidence lanes`,
654
+ `${path}.waves`,
655
+ ),
656
+ );
657
+ }
658
+ if (execution.maxConcurrency > policy.concurrency) {
659
+ errors.push(
660
+ issue(
661
+ 'PROFILE_CONCURRENCY_CAP_EXCEEDED',
662
+ `${requestedProfile} routing permits concurrency at most ${policy.concurrency}`,
663
+ `${path}.maxConcurrency`,
664
+ ),
665
+ );
666
+ }
667
+ if (conditions.length > policy.conditions) {
668
+ errors.push(
669
+ issue(
670
+ 'PROFILE_CONDITION_CAP_EXCEEDED',
671
+ `${requestedProfile} routing permits at most ${policy.conditions} conditional waves`,
672
+ `${path}.conditions`,
673
+ ),
674
+ );
675
+ }
676
+ const reconciliationIndexes = waves
677
+ .map((wave, index) => (wave?.mode === 'reconciliation' ? index : -1))
678
+ .filter((index) => index !== -1);
679
+ const requiresReconciliation = ['standard', 'thorough'].includes(
680
+ requestedProfile,
681
+ );
682
+ if (
683
+ reconciliationIndexes.length > 1 ||
684
+ (requiresReconciliation && reconciliationIndexes.length !== 1) ||
685
+ reconciliationIndexes.some((index) => index !== waves.length - 1) ||
686
+ reconciliationIndexes.some((index) => waves[index]?.conditional === true)
687
+ ) {
688
+ errors.push(
689
+ issue(
690
+ 'INVALID_TERMINAL_TOPOLOGY',
691
+ 'Standard and thorough routing require exactly one non-conditional terminal reconciliation wave',
692
+ `${path}.waves`,
693
+ ),
694
+ );
695
+ }
696
+
697
+ const waveIndexes = new Map(
698
+ waves
699
+ .map((wave, index) => (isObject(wave) ? [wave.waveId, index] : null))
700
+ .filter(Boolean),
701
+ );
702
+ const conditionIds = new Set();
703
+ const conditionDestinations = new Set();
704
+ const conditionFields = new Set([
705
+ 'conditionId',
706
+ 'destinationWaveId',
707
+ 'afterWaveIds',
708
+ 'predicate',
709
+ 'maxActivations',
710
+ ]);
711
+ const terminalIndex = reconciliationIndexes[0] ?? -1;
712
+ for (const [index, condition] of conditions.entries()) {
713
+ const conditionPath = `${path}.conditions[${index}]`;
714
+ if (
715
+ !isObject(condition) ||
716
+ Object.keys(condition).some((key) => !conditionFields.has(key))
717
+ ) {
718
+ errors.push(
719
+ issue(
720
+ 'INVALID_ROUTING_CONDITION',
721
+ 'Routing condition must be a closed object',
722
+ conditionPath,
723
+ ),
724
+ );
725
+ continue;
726
+ }
727
+ if (
728
+ typeof condition.conditionId !== 'string' ||
729
+ condition.conditionId.length === 0 ||
730
+ typeof condition.destinationWaveId !== 'string' ||
731
+ condition.destinationWaveId.length === 0
732
+ ) {
733
+ errors.push(
734
+ issue(
735
+ 'INVALID_ROUTING_CONDITION',
736
+ 'Condition and destination IDs must be non-empty strings',
737
+ conditionPath,
738
+ ),
739
+ );
740
+ continue;
741
+ }
742
+ if (
743
+ !Array.isArray(condition.afterWaveIds) ||
744
+ condition.afterWaveIds.length === 0
745
+ ) {
746
+ errors.push(
747
+ issue(
748
+ 'INVALID_CONDITION_DEPENDENCY',
749
+ 'Condition dependencies must contain at least one predecessor wave',
750
+ `${conditionPath}.afterWaveIds`,
751
+ ),
752
+ );
753
+ }
754
+ if (!conditionPredicates.includes(condition.predicate)) {
755
+ errors.push(
756
+ issue(
757
+ 'INVALID_CONDITION_PREDICATE',
758
+ 'Unknown routing condition predicate',
759
+ `${conditionPath}.predicate`,
760
+ ),
761
+ );
762
+ }
763
+ if (condition.maxActivations !== 1) {
764
+ errors.push(
765
+ issue(
766
+ 'INVALID_CONDITION_LIMIT',
767
+ 'Routing conditions must allow exactly one activation',
768
+ `${conditionPath}.maxActivations`,
769
+ ),
770
+ );
771
+ }
772
+ if (conditionIds.has(condition.conditionId)) {
773
+ errors.push(
774
+ issue(
775
+ 'DUPLICATE_ROUTING_ID',
776
+ `Duplicate condition ${condition.conditionId}`,
777
+ `${conditionPath}.conditionId`,
778
+ ),
779
+ );
780
+ }
781
+ conditionIds.add(condition.conditionId);
782
+ if (conditionDestinations.has(condition.destinationWaveId)) {
783
+ errors.push(
784
+ issue(
785
+ 'DUPLICATE_CONDITION_DESTINATION',
786
+ `Conditional wave ${condition.destinationWaveId} has more than one activating condition`,
787
+ `${conditionPath}.destinationWaveId`,
788
+ ),
789
+ );
790
+ }
791
+ conditionDestinations.add(condition.destinationWaveId);
792
+
793
+ const destinationIndex = waveIndexes.get(condition.destinationWaveId);
794
+ const destination =
795
+ destinationIndex === undefined ? null : waves[destinationIndex];
796
+ if (
797
+ !destination ||
798
+ destination.conditional !== true ||
799
+ destination.mode !== 'contradiction-resolution'
800
+ ) {
801
+ errors.push(
802
+ issue(
803
+ 'INVALID_CONDITION_DESTINATION',
804
+ 'Condition destination must name a conditional contradiction-resolution wave',
805
+ `${conditionPath}.destinationWaveId`,
806
+ ),
807
+ );
808
+ }
809
+ const afterIds = new Set();
810
+ for (const afterWaveId of Array.isArray(condition.afterWaveIds)
811
+ ? condition.afterWaveIds
812
+ : []) {
813
+ if (typeof afterWaveId !== 'string' || afterWaveId.length === 0) {
814
+ errors.push(
815
+ issue(
816
+ 'INVALID_CONDITION_DEPENDENCY',
817
+ 'Condition dependencies must be non-empty wave ID strings',
818
+ `${conditionPath}.afterWaveIds`,
819
+ ),
820
+ );
821
+ continue;
822
+ }
823
+ if (afterIds.has(afterWaveId)) {
824
+ errors.push(
825
+ issue(
826
+ 'DUPLICATE_CONDITION_DEPENDENCY',
827
+ `Condition repeats predecessor ${afterWaveId}`,
828
+ `${conditionPath}.afterWaveIds`,
829
+ ),
830
+ );
831
+ }
832
+ afterIds.add(afterWaveId);
833
+ const predecessorIndex = waveIndexes.get(afterWaveId);
834
+ if (predecessorIndex === undefined) {
835
+ errors.push(
836
+ issue(
837
+ 'UNKNOWN_CONDITION_WAVE',
838
+ `Unknown predecessor wave ${afterWaveId}`,
839
+ `${conditionPath}.afterWaveIds`,
840
+ ),
841
+ );
842
+ } else if (
843
+ destinationIndex !== undefined &&
844
+ predecessorIndex >= destinationIndex
845
+ ) {
846
+ errors.push(
847
+ issue(
848
+ 'NON_FORWARD_CONDITION',
849
+ `Condition predecessor ${afterWaveId} must appear before ${condition.destinationWaveId}`,
850
+ `${conditionPath}.afterWaveIds`,
851
+ ),
852
+ );
853
+ }
854
+ }
855
+ if (
856
+ terminalIndex !== -1 &&
857
+ destinationIndex !== undefined &&
858
+ destinationIndex >= terminalIndex
859
+ ) {
860
+ errors.push(
861
+ issue(
862
+ 'INVALID_TERMINAL_TOPOLOGY',
863
+ 'Conditional evidence waves must complete before the one terminal reconciliation',
864
+ `${conditionPath}.destinationWaveId`,
865
+ ),
866
+ );
867
+ }
868
+ }
869
+ for (const wave of waves) {
870
+ if (
871
+ wave?.conditional === true &&
872
+ wave.mode !== 'reconciliation' &&
873
+ !policy.orderedSingletonWaveModes.includes(wave.mode) &&
874
+ !conditionDestinations.has(wave.waveId)
875
+ ) {
876
+ errors.push(
877
+ issue(
878
+ 'MISSING_WAVE_CONDITION',
879
+ `Conditional wave ${wave.waveId} must be bound to exactly one activating condition`,
880
+ `${path}.waves`,
881
+ ),
882
+ );
883
+ }
884
+ }
885
+ const unconditionalContradiction = waves.find(
886
+ (wave) =>
887
+ wave?.mode === 'contradiction-resolution' && wave.conditional !== true,
888
+ );
889
+ if (unconditionalContradiction) {
890
+ errors.push(
891
+ issue(
892
+ 'UNCONDITIONAL_CONTRADICTION_RESOLUTION',
893
+ `Contradiction-resolution wave ${unconditionalContradiction.waveId} must be conditional and condition-bound`,
894
+ `${path}.waves`,
895
+ ),
896
+ );
897
+ }
898
+
899
+ const stageIndexes = policy.orderedSingletonWaveModes.map((mode) => ({
900
+ mode,
901
+ indexes: waves
902
+ .map((wave, index) => (wave?.mode === mode ? index : -1))
903
+ .filter((index) => index !== -1),
904
+ }));
905
+ const duplicateModes = stageIndexes
906
+ .filter(({ indexes }) => indexes.length > 1)
907
+ .map(({ mode }) => mode);
908
+ if (duplicateModes.length > 0) {
909
+ errors.push(
910
+ issue(
911
+ 'DUPLICATE_PROFILE_WAVE_MODE',
912
+ `${requestedProfile} routing requires one wave for singleton modes: ${duplicateModes.join(', ')}`,
913
+ `${path}.waves`,
914
+ ),
915
+ );
916
+ }
917
+ const missingModes = stageIndexes
918
+ .filter(({ indexes }) => indexes.length === 0)
919
+ .map(({ mode }) => mode);
920
+ if (missingModes.length > 0) {
921
+ errors.push(
922
+ issue(
923
+ 'INCOMPLETE_PROFILE_TOPOLOGY',
924
+ `${requestedProfile} routing is missing required non-conditional wave modes: ${missingModes.join(', ')}`,
925
+ `${path}.waves`,
926
+ ),
927
+ );
928
+ }
929
+ const conditionalRequired = stageIndexes.find(
930
+ ({ mode, indexes }) =>
931
+ mode !== 'reconciliation' &&
932
+ indexes.length === 1 &&
933
+ waves[indexes[0]]?.conditional,
934
+ );
935
+ if (conditionalRequired) {
936
+ errors.push(
937
+ issue(
938
+ 'INCOMPLETE_PROFILE_TOPOLOGY',
939
+ `${requestedProfile} required wave mode ${conditionalRequired.mode} must be non-conditional`,
940
+ `${path}.waves`,
941
+ ),
942
+ );
943
+ }
944
+ const orderedIndexes = stageIndexes.map(({ indexes }) => indexes[0]);
945
+ if (
946
+ orderedIndexes.every((index) => index !== undefined) &&
947
+ orderedIndexes.some(
948
+ (index, position) =>
949
+ position > 0 && index <= orderedIndexes[position - 1],
950
+ )
951
+ ) {
952
+ errors.push(
953
+ issue(
954
+ 'OUT_OF_ORDER_PROFILE_TOPOLOGY',
955
+ `${requestedProfile} routing must order singleton modes as: ${policy.orderedSingletonWaveModes.join(', ')}`,
956
+ `${path}.waves`,
957
+ ),
958
+ );
959
+ }
960
+ return errors;
961
+ }
962
+
963
+ export function validateExecution(value, errors, path = '$.execution') {
964
+ validateExecutionV2(value, errors, path);
965
+ }
966
+
400
967
  function validateManifest(value, errors) {
401
968
  closedObject(
402
969
  value,
@@ -409,6 +976,7 @@ function validateManifest(value, errors) {
409
976
  'execution',
410
977
  'artifacts',
411
978
  'gaps',
979
+ 'conditionOutcomes',
412
980
  ]),
413
981
  errors,
414
982
  );
@@ -500,13 +1068,76 @@ function validateManifest(value, errors) {
500
1068
  }
501
1069
  }
502
1070
  validateExecution(value.execution, errors, '$.execution');
503
- if (Array.isArray(value.sources))
504
- duplicateIds(value.sources, '$.sources', errors);
505
- if (Array.isArray(value.gaps)) duplicateIds(value.gaps, '$.gaps', errors);
506
- for (const [index, reference] of (value.artifacts ?? []).entries()) {
1071
+ errors.push(...validateV2ProfileTopology(value));
1072
+ requiredArray(value, 'conditionOutcomes', errors);
1073
+ const sources = Array.isArray(value.sources) ? value.sources : [];
1074
+ const artifacts = Array.isArray(value.artifacts) ? value.artifacts : [];
1075
+ const gaps = Array.isArray(value.gaps) ? value.gaps : [];
1076
+ const outcomeIds = new Set();
1077
+ for (const [index, outcome] of (Array.isArray(value.conditionOutcomes)
1078
+ ? value.conditionOutcomes
1079
+ : []
1080
+ ).entries()) {
1081
+ const outcomePath = `$.conditionOutcomes[${index}]`;
1082
+ if (!isObject(outcome)) {
1083
+ errors.push(
1084
+ issue(
1085
+ 'INVALID_CONDITION_OUTCOME',
1086
+ 'Condition outcome must be an object',
1087
+ outcomePath,
1088
+ ),
1089
+ );
1090
+ continue;
1091
+ }
1092
+ closedObject(
1093
+ outcome,
1094
+ new Set(['conditionId', 'disposition', 'reason', 'evidence']),
1095
+ errors,
1096
+ outcomePath,
1097
+ );
1098
+ requiredString(outcome, 'conditionId', errors, outcomePath);
1099
+ requiredString(outcome, 'reason', errors, outcomePath);
1100
+ requiredArray(outcome, 'evidence', errors, outcomePath);
1101
+ if (
1102
+ !['triggered', 'not-triggered', 'unresolved'].includes(
1103
+ outcome.disposition,
1104
+ )
1105
+ ) {
1106
+ errors.push(
1107
+ issue(
1108
+ 'INVALID_CONDITION_DISPOSITION',
1109
+ 'Condition disposition must be triggered, not-triggered, or unresolved',
1110
+ `${outcomePath}.disposition`,
1111
+ ),
1112
+ );
1113
+ }
1114
+ for (const [evidenceIndex, reference] of (Array.isArray(outcome.evidence)
1115
+ ? outcome.evidence
1116
+ : []
1117
+ ).entries()) {
1118
+ validateExactReference(
1119
+ reference,
1120
+ `${outcomePath}.evidence[${evidenceIndex}]`,
1121
+ errors,
1122
+ );
1123
+ }
1124
+ if (outcomeIds.has(outcome.conditionId)) {
1125
+ errors.push(
1126
+ issue(
1127
+ 'DUPLICATE_CONDITION_OUTCOME',
1128
+ `Condition ${outcome.conditionId} has more than one disposition`,
1129
+ `${outcomePath}.conditionId`,
1130
+ ),
1131
+ );
1132
+ }
1133
+ outcomeIds.add(outcome.conditionId);
1134
+ }
1135
+ duplicateIds(sources, '$.sources', errors);
1136
+ duplicateIds(gaps, '$.gaps', errors);
1137
+ for (const [index, reference] of artifacts.entries()) {
507
1138
  validateExactReference(reference, `$.artifacts[${index}]`, errors);
508
1139
  }
509
- for (const [index, source] of (value.sources ?? []).entries()) {
1140
+ for (const [index, source] of sources.entries()) {
510
1141
  for (const key of [
511
1142
  'kind',
512
1143
  'id',
@@ -615,7 +1246,7 @@ function validateManifest(value, errors) {
615
1246
  }
616
1247
  }
617
1248
  }
618
- for (const [index, gap] of (value.gaps ?? []).entries()) {
1249
+ for (const [index, gap] of gaps.entries()) {
619
1250
  requiredString(gap, 'id', errors, `$.gaps[${index}]`);
620
1251
  requiredString(gap, 'code', errors, `$.gaps[${index}]`);
621
1252
  requiredString(gap, 'message', errors, `$.gaps[${index}]`);
@@ -635,6 +1266,8 @@ function validateManifest(value, errors) {
635
1266
  'code',
636
1267
  'message',
637
1268
  'material',
1269
+ 'waveId',
1270
+ 'laneId',
638
1271
  'sourceIds',
639
1272
  'claimIds',
640
1273
  'coverageFindingIds',
@@ -642,6 +1275,10 @@ function validateManifest(value, errors) {
642
1275
  errors,
643
1276
  `$.gaps[${index}]`,
644
1277
  );
1278
+ if (Object.hasOwn(gap, 'waveId') || Object.hasOwn(gap, 'laneId')) {
1279
+ requiredString(gap, 'waveId', errors, `$.gaps[${index}]`);
1280
+ requiredString(gap, 'laneId', errors, `$.gaps[${index}]`);
1281
+ }
645
1282
  }
646
1283
  }
647
1284
 
@@ -757,10 +1394,16 @@ function validateLedger(value, errors) {
757
1394
  ? value.evidence.map((item) => item?.id).filter(Boolean)
758
1395
  : [],
759
1396
  );
760
- for (const [index, reference] of (value.inputArtifacts ?? []).entries()) {
1397
+ const inputArtifacts = Array.isArray(value.inputArtifacts)
1398
+ ? value.inputArtifacts
1399
+ : [];
1400
+ const evidenceItems = Array.isArray(value.evidence) ? value.evidence : [];
1401
+ const claims = Array.isArray(value.claims) ? value.claims : [];
1402
+ const transitions = Array.isArray(value.transitions) ? value.transitions : [];
1403
+ for (const [index, reference] of inputArtifacts.entries()) {
761
1404
  validateExactReference(reference, `$.inputArtifacts[${index}]`, errors);
762
1405
  }
763
- for (const [index, evidence] of (value.evidence ?? []).entries()) {
1406
+ for (const [index, evidence] of evidenceItems.entries()) {
764
1407
  closedObject(
765
1408
  evidence,
766
1409
  new Set([
@@ -857,7 +1500,7 @@ function validateLedger(value, errors) {
857
1500
  errors,
858
1501
  );
859
1502
  }
860
- for (const [index, claim] of (value.claims ?? []).entries()) {
1503
+ for (const [index, claim] of claims.entries()) {
861
1504
  closedObject(
862
1505
  claim,
863
1506
  new Set([
@@ -894,7 +1537,10 @@ function validateLedger(value, errors) {
894
1537
  requiredArray(claim, key, errors, `$.claims[${index}]`);
895
1538
  }
896
1539
  const linkedEvidenceIds = new Set();
897
- for (const [linkIndex, link] of (claim.evidence ?? []).entries()) {
1540
+ for (const [linkIndex, link] of (Array.isArray(claim.evidence)
1541
+ ? claim.evidence
1542
+ : []
1543
+ ).entries()) {
898
1544
  const linkPath = `$.claims[${index}].evidence[${linkIndex}]`;
899
1545
  if (!isObject(link)) {
900
1546
  errors.push(
@@ -940,8 +1586,9 @@ function validateLedger(value, errors) {
940
1586
  }
941
1587
  linkedEvidenceIds.add(link.evidenceId);
942
1588
  }
943
- for (const [referenceIndex, reference] of (
944
- claim.derivedFrom ?? []
1589
+ for (const [referenceIndex, reference] of (Array.isArray(claim.derivedFrom)
1590
+ ? claim.derivedFrom
1591
+ : []
945
1592
  ).entries()) {
946
1593
  validateExactReference(
947
1594
  reference,
@@ -950,7 +1597,7 @@ function validateLedger(value, errors) {
950
1597
  );
951
1598
  }
952
1599
  }
953
- for (const [index, transition] of (value.transitions ?? []).entries()) {
1600
+ for (const [index, transition] of transitions.entries()) {
954
1601
  closedObject(
955
1602
  transition,
956
1603
  new Set(['claimId', 'from', 'to']),
@@ -967,9 +1614,9 @@ function validateLedger(value, errors) {
967
1614
  );
968
1615
  }
969
1616
  }
970
- const claimIds = new Set((value.claims ?? []).map((claim) => claim.id));
1617
+ const claimIds = new Set(claims.map((claim) => claim.id));
971
1618
  const lastTransitionByClaim = new Map();
972
- for (const transition of value.transitions ?? []) {
1619
+ for (const transition of transitions) {
973
1620
  if (!claimIds.has(transition.claimId)) {
974
1621
  errors.push(
975
1622
  issue(
@@ -981,7 +1628,7 @@ function validateLedger(value, errors) {
981
1628
  }
982
1629
  lastTransitionByClaim.set(transition.claimId, transition);
983
1630
  }
984
- for (const claim of value.claims ?? []) {
1631
+ for (const claim of claims) {
985
1632
  const lastTransition = lastTransitionByClaim.get(claim.id);
986
1633
  if (value.revision === 1 && claim.status === 'provisional') {
987
1634
  if (lastTransition) {
@@ -1950,11 +2597,12 @@ export function validateArtifactShape(value) {
1950
2597
  ),
1951
2598
  );
1952
2599
  }
1953
- if (value.schemaVersion !== SCHEMA_VERSION) {
2600
+ const versions = supportedSchemaVersions.get(value.kind);
2601
+ if (versions && !versions.has(value.schemaVersion)) {
1954
2602
  errors.push(
1955
2603
  issue(
1956
2604
  'UNSUPPORTED_SCHEMA_VERSION',
1957
- `Expected schemaVersion ${SCHEMA_VERSION}`,
2605
+ `Unsupported schemaVersion ${value.schemaVersion} for ${value.kind}`,
1958
2606
  '$.schemaVersion',
1959
2607
  ),
1960
2608
  );