@farmslot/protocol 0.37.0 → 0.39.0

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 (193) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/agents/reviewer-context.d.ts +4 -4
  3. package/dist/agents/reviewer-context.d.ts.map +1 -1
  4. package/dist/agents/reviewer-context.js +10 -10
  5. package/dist/agents/reviewer-context.js.map +1 -1
  6. package/dist/agents/roles.d.ts +2 -2
  7. package/dist/agents/roles.d.ts.map +1 -1
  8. package/dist/agents/roles.js +5 -9
  9. package/dist/agents/roles.js.map +1 -1
  10. package/dist/checklist-target.d.ts +6 -6
  11. package/dist/checklist-target.d.ts.map +1 -1
  12. package/dist/checklist-target.js +11 -11
  13. package/dist/checklist-target.js.map +1 -1
  14. package/dist/contracts/acceptance.d.ts +19 -34
  15. package/dist/contracts/acceptance.d.ts.map +1 -1
  16. package/dist/contracts/acceptance.js +11 -156
  17. package/dist/contracts/acceptance.js.map +1 -1
  18. package/dist/contracts/agents.d.ts +4 -2
  19. package/dist/contracts/agents.d.ts.map +1 -1
  20. package/dist/contracts/agents.js +1 -2
  21. package/dist/contracts/agents.js.map +1 -1
  22. package/dist/contracts/analytics.d.ts +2 -2
  23. package/dist/contracts/analytics.d.ts.map +1 -1
  24. package/dist/contracts/backlog.d.ts +16 -7
  25. package/dist/contracts/backlog.d.ts.map +1 -1
  26. package/dist/contracts/backlog.js.map +1 -1
  27. package/dist/contracts/config.d.ts +12 -3
  28. package/dist/contracts/config.d.ts.map +1 -1
  29. package/dist/contracts/config.js +32 -0
  30. package/dist/contracts/config.js.map +1 -1
  31. package/dist/contracts/dispatch.d.ts +7 -4
  32. package/dist/contracts/dispatch.d.ts.map +1 -1
  33. package/dist/contracts/family.d.ts +2 -2
  34. package/dist/contracts/family.d.ts.map +1 -1
  35. package/dist/contracts/gate-park.d.ts +4 -1
  36. package/dist/contracts/gate-park.d.ts.map +1 -1
  37. package/dist/contracts/gate-park.js +3 -0
  38. package/dist/contracts/gate-park.js.map +1 -1
  39. package/dist/contracts/index.d.ts +1 -0
  40. package/dist/contracts/index.d.ts.map +1 -1
  41. package/dist/contracts/index.js +1 -0
  42. package/dist/contracts/index.js.map +1 -1
  43. package/dist/contracts/pr-monitoring.d.ts +9 -9
  44. package/dist/contracts/pr-monitoring.d.ts.map +1 -1
  45. package/dist/contracts/pr-rules.d.ts +5 -7
  46. package/dist/contracts/pr-rules.d.ts.map +1 -1
  47. package/dist/contracts/pr-rules.js +4 -6
  48. package/dist/contracts/pr-rules.js.map +1 -1
  49. package/dist/contracts/qa.d.ts +2 -2
  50. package/dist/contracts/qa.d.ts.map +1 -1
  51. package/dist/contracts/qa.js +5 -9
  52. package/dist/contracts/qa.js.map +1 -1
  53. package/dist/contracts/recipe-conformance.d.ts +51 -0
  54. package/dist/contracts/recipe-conformance.d.ts.map +1 -1
  55. package/dist/contracts/recipe-conformance.js +134 -1
  56. package/dist/contracts/recipe-conformance.js.map +1 -1
  57. package/dist/contracts/resources.d.ts +1 -1
  58. package/dist/contracts/resources.d.ts.map +1 -1
  59. package/dist/contracts/resources.js.map +1 -1
  60. package/dist/contracts/reviews.d.ts +19 -3
  61. package/dist/contracts/reviews.d.ts.map +1 -1
  62. package/dist/contracts/reviews.js +2 -2
  63. package/dist/contracts/reviews.js.map +1 -1
  64. package/dist/contracts/runner-mobile-keys.d.ts +217 -0
  65. package/dist/contracts/runner-mobile-keys.d.ts.map +1 -0
  66. package/dist/contracts/runner-mobile-keys.js +189 -0
  67. package/dist/contracts/runner-mobile-keys.js.map +1 -0
  68. package/dist/contracts/runs.d.ts +105 -91
  69. package/dist/contracts/runs.d.ts.map +1 -1
  70. package/dist/contracts/runs.js +10 -14
  71. package/dist/contracts/runs.js.map +1 -1
  72. package/dist/contracts/runtime-capabilities.d.ts +2 -0
  73. package/dist/contracts/runtime-capabilities.d.ts.map +1 -1
  74. package/dist/contracts/runtime-capabilities.js.map +1 -1
  75. package/dist/contracts/template-placeholders.d.ts +0 -6
  76. package/dist/contracts/template-placeholders.d.ts.map +1 -1
  77. package/dist/contracts/template-placeholders.js +22 -4
  78. package/dist/contracts/template-placeholders.js.map +1 -1
  79. package/dist/contracts/worker-terminal.d.ts +19 -0
  80. package/dist/contracts/worker-terminal.d.ts.map +1 -1
  81. package/dist/contracts/worker-terminal.js.map +1 -1
  82. package/dist/index.d.ts +3 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +3 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/integrations/pr-monitoring.d.ts +5 -0
  87. package/dist/integrations/pr-monitoring.d.ts.map +1 -1
  88. package/dist/integrations/pr-monitoring.js +54 -9
  89. package/dist/integrations/pr-monitoring.js.map +1 -1
  90. package/dist/integrations/pr-rule-config.d.ts.map +1 -1
  91. package/dist/integrations/pr-rule-config.js +21 -25
  92. package/dist/integrations/pr-rule-config.js.map +1 -1
  93. package/dist/integrations/pr-workflow-defaults.d.ts.map +1 -1
  94. package/dist/integrations/pr-workflow-defaults.js +2 -5
  95. package/dist/integrations/pr-workflow-defaults.js.map +1 -1
  96. package/dist/node/pack-portability.d.ts +19 -0
  97. package/dist/node/pack-portability.d.ts.map +1 -0
  98. package/dist/node/pack-portability.js +192 -0
  99. package/dist/node/pack-portability.js.map +1 -0
  100. package/dist/node/slot-by-repo.d.ts +6 -0
  101. package/dist/node/slot-by-repo.d.ts.map +1 -1
  102. package/dist/node/slot-by-repo.js +27 -15
  103. package/dist/node/slot-by-repo.js.map +1 -1
  104. package/dist/recipe/artifact.d.ts.map +1 -1
  105. package/dist/recipe/artifact.js +20 -4
  106. package/dist/recipe/artifact.js.map +1 -1
  107. package/dist/recipe/document.d.ts +2 -0
  108. package/dist/recipe/document.d.ts.map +1 -1
  109. package/dist/recipe/document.js +38 -35
  110. package/dist/recipe/document.js.map +1 -1
  111. package/dist/recipe/evidence.d.ts +128 -0
  112. package/dist/recipe/evidence.d.ts.map +1 -0
  113. package/dist/recipe/evidence.js +141 -0
  114. package/dist/recipe/evidence.js.map +1 -0
  115. package/dist/recipe/hud-intent.d.ts +56 -0
  116. package/dist/recipe/hud-intent.d.ts.map +1 -0
  117. package/dist/recipe/hud-intent.js +46 -0
  118. package/dist/recipe/hud-intent.js.map +1 -0
  119. package/dist/recipe/index.d.ts +4 -2
  120. package/dist/recipe/index.d.ts.map +1 -1
  121. package/dist/recipe/index.js +4 -2
  122. package/dist/recipe/index.js.map +1 -1
  123. package/dist/recipe/recording.d.ts +7 -0
  124. package/dist/recipe/recording.d.ts.map +1 -1
  125. package/dist/recipe/recording.js +48 -0
  126. package/dist/recipe/recording.js.map +1 -1
  127. package/dist/recipe/workflow.d.ts.map +1 -1
  128. package/dist/recipe/workflow.js +23 -1
  129. package/dist/recipe/workflow.js.map +1 -1
  130. package/dist/recipes/step-io.d.ts +8 -4
  131. package/dist/recipes/step-io.d.ts.map +1 -1
  132. package/dist/recipes/step-io.js.map +1 -1
  133. package/dist/rpc/copilot-runtime.d.ts +1 -1
  134. package/dist/rpc/copilot-runtime.d.ts.map +1 -1
  135. package/dist/rpc/dispatch.d.ts +10 -6
  136. package/dist/rpc/dispatch.d.ts.map +1 -1
  137. package/dist/rpc/native-session.d.ts +1 -0
  138. package/dist/rpc/native-session.d.ts.map +1 -1
  139. package/dist/rpc/native-session.js.map +1 -1
  140. package/dist/rpc/nodes.d.ts +15 -0
  141. package/dist/rpc/nodes.d.ts.map +1 -1
  142. package/dist/rpc/nodes.js +32 -1
  143. package/dist/rpc/nodes.js.map +1 -1
  144. package/dist/rpc/registry.d.ts +1 -0
  145. package/dist/rpc/registry.d.ts.map +1 -1
  146. package/dist/rpc/registry.js +1 -0
  147. package/dist/rpc/registry.js.map +1 -1
  148. package/dist/rpc/roadmap.d.ts +2 -0
  149. package/dist/rpc/roadmap.d.ts.map +1 -1
  150. package/dist/rpc/run.d.ts +20 -3
  151. package/dist/rpc/run.d.ts.map +1 -1
  152. package/dist/rpc/run.js +1 -0
  153. package/dist/rpc/run.js.map +1 -1
  154. package/dist/rpc/slot.d.ts +1 -0
  155. package/dist/rpc/slot.d.ts.map +1 -1
  156. package/dist/rpc/tmux.d.ts +10 -0
  157. package/dist/rpc/tmux.d.ts.map +1 -1
  158. package/dist/runs/ci-decision-context.d.ts +40 -0
  159. package/dist/runs/ci-decision-context.d.ts.map +1 -0
  160. package/dist/runs/ci-decision-context.js +129 -0
  161. package/dist/runs/ci-decision-context.js.map +1 -0
  162. package/dist/runs/decision-resolution.d.ts +37 -1
  163. package/dist/runs/decision-resolution.d.ts.map +1 -1
  164. package/dist/runs/decision-resolution.js +36 -0
  165. package/dist/runs/decision-resolution.js.map +1 -1
  166. package/dist/runs/family-iteration-ledger.js +1 -1
  167. package/dist/runs/family-iteration-ledger.js.map +1 -1
  168. package/dist/runs/publish-approval.d.ts +20 -0
  169. package/dist/runs/publish-approval.d.ts.map +1 -0
  170. package/dist/runs/publish-approval.js +29 -0
  171. package/dist/runs/publish-approval.js.map +1 -0
  172. package/dist/runs/review-chain.d.ts +2 -2
  173. package/dist/runs/review-chain.d.ts.map +1 -1
  174. package/dist/runs/review-chain.js +4 -4
  175. package/dist/runs/review-chain.js.map +1 -1
  176. package/dist/runs/review-policy.d.ts +9 -0
  177. package/dist/runs/review-policy.d.ts.map +1 -0
  178. package/dist/runs/review-policy.js +34 -0
  179. package/dist/runs/review-policy.js.map +1 -0
  180. package/dist/runs/review-retries.d.ts +10 -10
  181. package/dist/runs/review-retries.d.ts.map +1 -1
  182. package/dist/runs/review-retries.js +17 -22
  183. package/dist/runs/review-retries.js.map +1 -1
  184. package/dist/runs/slot-resolution.js +1 -1
  185. package/dist/runs/slot-resolution.js.map +1 -1
  186. package/dist/transport/signal.d.ts +3 -3
  187. package/dist/transport/signal.d.ts.map +1 -1
  188. package/dist/version.d.ts +1 -1
  189. package/dist/version.js +1 -1
  190. package/package.json +14 -2
  191. package/schemas/recipe-v1.schema.json +113 -22
  192. package/scripts/acceptance.d.mts +16 -0
  193. package/scripts/acceptance.mjs +233 -0
@@ -40,6 +40,19 @@
40
40
  "type": "string",
41
41
  "minLength": 1,
42
42
  "description": "Human-readable statement that successful linked nodes establish."
43
+ },
44
+ "kind": {
45
+ "enum": ["acceptance", "qa-case", "qa-check"],
46
+ "description": "Acceptance criterion, QA case or QA check proved by the recipe."
47
+ },
48
+ "parentId": {
49
+ "type": "string",
50
+ "minLength": 1,
51
+ "description": "Registered parent claim whose outcome includes this required check."
52
+ },
53
+ "proofMode": {
54
+ "enum": ["state", "visual", "mixed"],
55
+ "description": "State assertions, visual review, or both are required to prove this claim."
43
56
  }
44
57
  },
45
58
  "additionalProperties": false
@@ -67,14 +80,21 @@
67
80
  "required": {
68
81
  "type": "array",
69
82
  "description": "Parameter names callers must provide when no default satisfies them.",
70
- "items": { "type": "string", "minLength": 1 },
83
+ "items": {
84
+ "type": "string",
85
+ "minLength": 1
86
+ },
71
87
  "uniqueItems": true
72
88
  },
73
89
  "properties": {
74
90
  "type": "object",
75
91
  "description": "Named parameter definitions available to callers and call nodes.",
76
- "propertyNames": { "pattern": "^[A-Za-z0-9_-]+$" },
77
- "additionalProperties": { "$ref": "#/$defs/paramSchema" }
92
+ "propertyNames": {
93
+ "pattern": "^[A-Za-z0-9_-]+$"
94
+ },
95
+ "additionalProperties": {
96
+ "$ref": "#/$defs/paramSchema"
97
+ }
78
98
  }
79
99
  },
80
100
  "additionalProperties": false
@@ -86,10 +106,14 @@
86
106
  "type": {
87
107
  "description": "Allowed JSON value type or types for this parameter.",
88
108
  "oneOf": [
89
- { "enum": ["string", "number", "integer", "boolean", "object", "array"] },
109
+ {
110
+ "enum": ["string", "number", "integer", "boolean", "object", "array"]
111
+ },
90
112
  {
91
113
  "type": "array",
92
- "items": { "enum": ["string", "number", "integer", "boolean", "object", "array"] },
114
+ "items": {
115
+ "enum": ["string", "number", "integer", "boolean", "object", "array"]
116
+ },
93
117
  "minItems": 1,
94
118
  "uniqueItems": true
95
119
  }
@@ -115,14 +139,21 @@
115
139
  "required": {
116
140
  "type": "array",
117
141
  "description": "Required keys within an object parameter.",
118
- "items": { "type": "string", "minLength": 1 },
142
+ "items": {
143
+ "type": "string",
144
+ "minLength": 1
145
+ },
119
146
  "uniqueItems": true
120
147
  },
121
148
  "properties": {
122
149
  "type": "object",
123
150
  "description": "Named child schemas for an object parameter.",
124
- "propertyNames": { "pattern": "^[A-Za-z0-9_-]+$" },
125
- "additionalProperties": { "$ref": "#/$defs/paramSchema" }
151
+ "propertyNames": {
152
+ "pattern": "^[A-Za-z0-9_-]+$"
153
+ },
154
+ "additionalProperties": {
155
+ "$ref": "#/$defs/paramSchema"
156
+ }
126
157
  },
127
158
  "items": {
128
159
  "$ref": "#/$defs/paramSchema",
@@ -152,7 +183,10 @@
152
183
  },
153
184
  "proofLinks": {
154
185
  "type": "array",
155
- "items": { "type": "string", "minLength": 1 },
186
+ "items": {
187
+ "type": "string",
188
+ "minLength": 1
189
+ },
156
190
  "uniqueItems": true
157
191
  },
158
192
  "transition": {
@@ -166,7 +200,16 @@
166
200
  "description": "Node id to execute after this node succeeds."
167
201
  }
168
202
  },
169
- "not": { "anyOf": [{ "required": ["cases"] }, { "required": ["default"] }] }
203
+ "not": {
204
+ "anyOf": [
205
+ {
206
+ "required": ["cases"]
207
+ },
208
+ {
209
+ "required": ["default"]
210
+ }
211
+ ]
212
+ }
170
213
  },
171
214
  {
172
215
  "required": ["cases", "default"],
@@ -174,7 +217,10 @@
174
217
  "cases": {
175
218
  "type": "object",
176
219
  "minProperties": 1,
177
- "additionalProperties": { "type": "string", "minLength": 1 },
220
+ "additionalProperties": {
221
+ "type": "string",
222
+ "minLength": 1
223
+ },
178
224
  "description": "Maps an action result case to the next node id."
179
225
  },
180
226
  "default": {
@@ -183,7 +229,9 @@
183
229
  "description": "Node id used when no declared result case matches."
184
230
  }
185
231
  },
186
- "not": { "required": ["next"] }
232
+ "not": {
233
+ "required": ["next"]
234
+ }
187
235
  }
188
236
  ]
189
237
  },
@@ -194,7 +242,9 @@
194
242
  "action": {
195
243
  "type": "string",
196
244
  "minLength": 1,
197
- "not": { "enum": ["call", "end"] },
245
+ "not": {
246
+ "enum": ["call", "end"]
247
+ },
198
248
  "description": "Registered action name to execute; action-specific fields are declared by its manifest."
199
249
  },
200
250
  "intent": {
@@ -210,7 +260,10 @@
210
260
  "cases": {
211
261
  "type": "object",
212
262
  "minProperties": 1,
213
- "additionalProperties": { "type": "string", "minLength": 1 },
263
+ "additionalProperties": {
264
+ "type": "string",
265
+ "minLength": 1
266
+ },
214
267
  "description": "Maps an action result case to the next node id."
215
268
  },
216
269
  "default": {
@@ -225,9 +278,24 @@
225
278
  "visual_review": {
226
279
  "$ref": "#/$defs/visualReviewRelationship",
227
280
  "description": "Optional visual-review hierarchy and related-surface links for ui.capture_surface and ui.screenshot nodes."
281
+ },
282
+ "retryGroup": {
283
+ "type": "string",
284
+ "minLength": 1,
285
+ "description": "Declared retry identity; earlier failed attempts remain in the trace."
286
+ },
287
+ "retryAttempts": {
288
+ "type": "integer",
289
+ "minimum": 1,
290
+ "maximum": 3,
291
+ "description": "Maximum attempts for a declared retry, including the first execution."
228
292
  }
229
293
  },
230
- "allOf": [{ "$ref": "#/$defs/transition" }],
294
+ "allOf": [
295
+ {
296
+ "$ref": "#/$defs/transition"
297
+ }
298
+ ],
231
299
  "additionalProperties": true
232
300
  },
233
301
  "visualReviewRelationship": {
@@ -261,7 +329,10 @@
261
329
  },
262
330
  "related": {
263
331
  "type": "array",
264
- "items": { "type": "string", "minLength": 1 },
332
+ "items": {
333
+ "type": "string",
334
+ "minLength": 1
335
+ },
265
336
  "uniqueItems": true,
266
337
  "description": "Related visual capture node ids within this recipe."
267
338
  }
@@ -284,7 +355,9 @@
284
355
  "ref": {
285
356
  "type": "string",
286
357
  "minLength": 1,
287
- "not": { "pattern": "\\{\\{\\s*(?:params|outputs)\\." },
358
+ "not": {
359
+ "pattern": "\\{\\{\\s*(?:params|outputs)\\."
360
+ },
288
361
  "description": "Static recipe id resolved from configured recipe libraries."
289
362
  },
290
363
  "params": {
@@ -299,6 +372,14 @@
299
372
  "type": "string",
300
373
  "minLength": 1,
301
374
  "description": "Node id to execute after the called recipe succeeds."
375
+ },
376
+ "claimBindings": {
377
+ "type": "object",
378
+ "additionalProperties": {
379
+ "type": "string",
380
+ "minLength": 1
381
+ },
382
+ "description": "Map declared child claims to registered parent claims; unbound child ids are qualified by this call."
302
383
  }
303
384
  },
304
385
  "additionalProperties": false
@@ -320,9 +401,15 @@
320
401
  },
321
402
  "workflowNode": {
322
403
  "oneOf": [
323
- { "$ref": "#/$defs/actionNode" },
324
- { "$ref": "#/$defs/callNode" },
325
- { "$ref": "#/$defs/endNode" }
404
+ {
405
+ "$ref": "#/$defs/actionNode"
406
+ },
407
+ {
408
+ "$ref": "#/$defs/callNode"
409
+ },
410
+ {
411
+ "$ref": "#/$defs/endNode"
412
+ }
326
413
  ]
327
414
  },
328
415
  "workflow": {
@@ -342,8 +429,12 @@
342
429
  "nodes": {
343
430
  "type": "object",
344
431
  "minProperties": 1,
345
- "propertyNames": { "pattern": "^[A-Za-z0-9_-]+$" },
346
- "additionalProperties": { "$ref": "#/$defs/workflowNode" },
432
+ "propertyNames": {
433
+ "pattern": "^[A-Za-z0-9_-]+$"
434
+ },
435
+ "additionalProperties": {
436
+ "$ref": "#/$defs/workflowNode"
437
+ },
347
438
  "description": "Named action, recipe-call, and terminal nodes in the executable graph."
348
439
  }
349
440
  },
@@ -0,0 +1,16 @@
1
+ export const ACCEPTANCE_STATUS_ARTIFACT: 'artifacts/acceptance-status.json';
2
+ export const ACCEPTANCE_VERDICTS: readonly [
3
+ 'proven',
4
+ 'weak',
5
+ 'missing',
6
+ 'untestable',
7
+ 'failed',
8
+ 'awaiting-review',
9
+ ];
10
+ export const ACCEPTANCE_PROOF_MODES: readonly ['state', 'visual', 'mixed'];
11
+ export const ACCEPTANCE_CRITERION_ID_PATTERN: RegExp;
12
+ export function acceptanceCriterionId(index: number): string;
13
+ export function validateAcceptanceStatusLedger(value: unknown): string[];
14
+ export function acceptanceCriteriaView(criteria: readonly unknown[], ledger: unknown): unknown[];
15
+ export function summarizeAcceptanceStatus(ledger: unknown, criteria?: readonly unknown[]): unknown;
16
+ export function renderAcceptanceCoverage(ledger: unknown, criteria?: readonly unknown[]): string;
@@ -0,0 +1,233 @@
1
+ // Portable acceptance contract. Operator scripts and clients consume the same implementation.
2
+ const ACCEPTANCE_STATUS_ARTIFACT = 'artifacts/acceptance-status.json';
3
+ const ACCEPTANCE_VERDICTS = [
4
+ 'proven',
5
+ 'weak',
6
+ 'missing',
7
+ 'untestable',
8
+ 'failed',
9
+ 'awaiting-review',
10
+ ];
11
+ const ACCEPTANCE_PROOF_MODES = ['state', 'visual', 'mixed'];
12
+ const ACCEPTANCE_CRITERION_ID_PATTERN = /^AC-[1-9][0-9]*$/;
13
+
14
+ function acceptanceCriterionId(index) {
15
+ return `AC-${index + 1}`;
16
+ }
17
+
18
+ function isRecord(value) {
19
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
20
+ }
21
+
22
+ function isNonEmptyString(value) {
23
+ return typeof value === 'string' && value.trim().length > 0;
24
+ }
25
+
26
+ function validateAcceptanceStatusLedger(value) {
27
+ const issues = [];
28
+ if (!isRecord(value)) return ['acceptance ledger: expected object'];
29
+ if (value.schemaVersion !== 1 && value.schemaVersion !== 2)
30
+ issues.push('acceptance ledger schemaVersion: expected 1 or 2');
31
+ if (value.schemaVersion === 2 && !isNonEmptyString(value.recipeRunId))
32
+ issues.push('acceptance ledger recipeRunId: expected execution identity');
33
+ if (value.schemaVersion === 2) {
34
+ const recording = value.recording;
35
+ if (
36
+ !isRecord(recording) ||
37
+ ![
38
+ 'not-required',
39
+ 'explicit-off',
40
+ 'recording',
41
+ 'finalizing',
42
+ 'complete',
43
+ 'incomplete',
44
+ 'failed',
45
+ ].includes(recording.status) ||
46
+ !Array.isArray(recording.segments) ||
47
+ recording.segments.some(
48
+ (segment) =>
49
+ !isRecord(segment) ||
50
+ !isNonEmptyString(segment.videoPath) ||
51
+ !/^sha256:[a-f0-9]{64}$/.test(segment.videoDigest),
52
+ )
53
+ )
54
+ issues.push('acceptance ledger recording: invalid capture state or segments');
55
+ }
56
+ if (!Array.isArray(value.criteria)) {
57
+ issues.push('acceptance ledger criteria: expected array');
58
+ return issues;
59
+ }
60
+ if (
61
+ value.schemaVersion === 2 &&
62
+ value.judgments !== undefined &&
63
+ (!Array.isArray(value.judgments) ||
64
+ value.judgments.some(
65
+ (judgment) =>
66
+ !isRecord(judgment) ||
67
+ judgment.recipeRunId !== value.recipeRunId ||
68
+ !isNonEmptyString(judgment.claimId) ||
69
+ !['confirmed', 'rejected'].includes(judgment.verdict) ||
70
+ !isNonEmptyString(judgment.actor) ||
71
+ !isNonEmptyString(judgment.reason) ||
72
+ !Number.isFinite(Date.parse(judgment.judgedAt)) ||
73
+ !Array.isArray(judgment.moments),
74
+ ))
75
+ )
76
+ issues.push('acceptance ledger judgments: invalid visual review');
77
+ const seen = new Set();
78
+ value.criteria.forEach((entry, index) => {
79
+ const prefix = `acceptance ledger criteria[${index}]`;
80
+ if (!isRecord(entry)) {
81
+ issues.push(`${prefix}: expected object`);
82
+ return;
83
+ }
84
+ if (
85
+ !isNonEmptyString(entry.id) ||
86
+ (value.schemaVersion === 1 && !ACCEPTANCE_CRITERION_ID_PATTERN.test(entry.id))
87
+ ) {
88
+ issues.push(`${prefix}.id: expected AC-<N>`);
89
+ } else if (seen.has(entry.id)) {
90
+ issues.push(`${prefix}.id: duplicate ${entry.id}`);
91
+ } else {
92
+ seen.add(entry.id);
93
+ }
94
+ if (typeof entry.text !== 'string') issues.push(`${prefix}.text: expected string`);
95
+ if (!ACCEPTANCE_VERDICTS.includes(entry.verdict)) {
96
+ issues.push(`${prefix}.verdict: expected one of ${ACCEPTANCE_VERDICTS.join(', ')}`);
97
+ }
98
+ if (entry.proofMode !== undefined && !ACCEPTANCE_PROOF_MODES.includes(entry.proofMode)) {
99
+ issues.push(`${prefix}.proofMode: expected one of ${ACCEPTANCE_PROOF_MODES.join(', ')}`);
100
+ }
101
+ for (const key of ['evidence', 'recipeNodes']) {
102
+ const list = entry[key];
103
+ if (!Array.isArray(list) || list.some((item) => !isNonEmptyString(item))) {
104
+ issues.push(`${prefix}.${key}: expected an array of non-empty strings`);
105
+ }
106
+ }
107
+ if (value.schemaVersion === 2) {
108
+ if (!['acceptance', 'qa-case', 'qa-check'].includes(entry.kind))
109
+ issues.push(`${prefix}.kind: invalid claim kind`);
110
+ if (!ACCEPTANCE_PROOF_MODES.includes(entry.proofMode))
111
+ issues.push(`${prefix}.proofMode: required`);
112
+ if (
113
+ !Array.isArray(entry.assertionIndices) ||
114
+ entry.assertionIndices.some((index) => !Number.isInteger(index) || index < 0)
115
+ )
116
+ issues.push(`${prefix}.assertionIndices: invalid trace references`);
117
+ if (
118
+ !Array.isArray(entry.moments) ||
119
+ entry.moments.some(
120
+ (moment) =>
121
+ !isRecord(moment) ||
122
+ moment.recipeRunId !== value.recipeRunId ||
123
+ !/^sha256:[a-f0-9]{64}$/.test(moment.videoDigest) ||
124
+ !Number.isInteger(moment.traceIndex) ||
125
+ moment.traceIndex < 0,
126
+ )
127
+ )
128
+ issues.push(`${prefix}.moments: invalid moment references`);
129
+ }
130
+ if (entry.note !== undefined && typeof entry.note !== 'string') {
131
+ issues.push(`${prefix}.note: expected string`);
132
+ }
133
+ if (value.schemaVersion === 1 && !isNonEmptyString(entry.updatedAt)) {
134
+ issues.push(`${prefix}.updatedAt: expected non-empty string`);
135
+ }
136
+ });
137
+ return issues;
138
+ }
139
+
140
+ function acceptanceCriteriaView(criteria, ledger) {
141
+ const byId = new Map((ledger?.criteria ?? []).map((entry) => [entry.id, entry]));
142
+ const rows = criteria.map((criterion) => ({
143
+ ...criterion,
144
+ status: byId.get(criterion.id) ?? null,
145
+ }));
146
+ for (const entry of ledger?.criteria ?? []) {
147
+ if (!criteria.some((criterion) => criterion.id === entry.id)) {
148
+ rows.push({ id: entry.id, text: entry.text, status: entry });
149
+ }
150
+ }
151
+ return rows;
152
+ }
153
+
154
+ function summarizeAcceptanceStatus(ledger, criteria) {
155
+ const total = criteria
156
+ ? Math.max(criteria.length, ledger.criteria.length)
157
+ : ledger.criteria.length;
158
+ const summary = {
159
+ proven: 0,
160
+ weak: 0,
161
+ failed: 0,
162
+ 'awaiting-review': 0,
163
+ missing: 0,
164
+ untestable: 0,
165
+ unrecorded: 0,
166
+ total,
167
+ };
168
+ for (const criterion of ledger.criteria) summary[criterion.verdict] += 1;
169
+ summary.unrecorded =
170
+ total - ACCEPTANCE_VERDICTS.reduce((count, verdict) => count + summary[verdict], 0);
171
+ return summary;
172
+ }
173
+
174
+ function cell(value) {
175
+ return (
176
+ value
177
+ .replace(/\|/g, '\\|')
178
+ .replace(/\s*\n\s*/g, ' ')
179
+ .trim() || '-'
180
+ );
181
+ }
182
+
183
+ function list(values) {
184
+ return values.length > 0 ? cell(values.join(', ')) : '-';
185
+ }
186
+
187
+ function renderAcceptanceCoverage(ledger, criteria = ledger.criteria) {
188
+ const summary = summarizeAcceptanceStatus(ledger, criteria);
189
+ const rows = acceptanceCriteriaView(criteria, ledger);
190
+ const untestable = ledger.criteria
191
+ .filter((criterion) => criterion.verdict === 'untestable')
192
+ .map((criterion) => criterion.id);
193
+ const lines = [
194
+ '## Recipe coverage',
195
+ '',
196
+ '| AC | Criterion | Verdict | Proof mode | Recipe nodes | Evidence | Note |',
197
+ '| --- | --- | --- | --- | --- | --- | --- |',
198
+ ];
199
+ for (const row of rows) {
200
+ const status = row.status;
201
+ lines.push(
202
+ `| ${row.id} | ${cell(row.text)} | ${status ? status.verdict.toUpperCase() : 'NO VERDICT'} | ` +
203
+ `${status?.proofMode ?? '-'} | ${list(status?.recipeNodes ?? [])} | ` +
204
+ `${list(status?.evidence ?? [])} | ${cell(status?.note ?? '')} |`,
205
+ );
206
+ }
207
+ lines.push(
208
+ '',
209
+ `Overall recipe coverage: ${summary.proven}/${summary.total} ACs PROVEN ` +
210
+ `(untestable: ${untestable.length > 0 ? untestable.join(', ') : 'none'}, ` +
211
+ `weak: ${summary.weak}, missing: ${summary.missing}` +
212
+ (summary.failed ? `, failed: ${summary.failed}` : '') +
213
+ (summary['awaiting-review'] ? `, awaiting review: ${summary['awaiting-review']}` : '') +
214
+ `${summary.unrecorded > 0 ? `, no verdict: ${summary.unrecorded}` : ''})`,
215
+ '',
216
+ );
217
+ return lines.join('\n');
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // task-dir IO
222
+
223
+ export {
224
+ ACCEPTANCE_CRITERION_ID_PATTERN,
225
+ ACCEPTANCE_PROOF_MODES,
226
+ ACCEPTANCE_STATUS_ARTIFACT,
227
+ ACCEPTANCE_VERDICTS,
228
+ acceptanceCriteriaView,
229
+ acceptanceCriterionId,
230
+ renderAcceptanceCoverage,
231
+ summarizeAcceptanceStatus,
232
+ validateAcceptanceStatusLedger,
233
+ };