@cleocode/contracts 2026.5.120 → 2026.5.122
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.
- package/dist/__tests__/changesets.test.d.ts +8 -0
- package/dist/__tests__/changesets.test.d.ts.map +1 -0
- package/dist/__tests__/changesets.test.js +146 -0
- package/dist/__tests__/changesets.test.js.map +1 -0
- package/dist/__tests__/evidence-atom-schema.test.js +75 -1
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
- package/dist/__tests__/satisfies-atom.test.d.ts +24 -0
- package/dist/__tests__/satisfies-atom.test.d.ts.map +1 -0
- package/dist/__tests__/satisfies-atom.test.js +302 -0
- package/dist/__tests__/satisfies-atom.test.js.map +1 -0
- package/dist/__tests__/validator-types.test.d.ts +13 -0
- package/dist/__tests__/validator-types.test.d.ts.map +1 -0
- package/dist/__tests__/validator-types.test.js +293 -0
- package/dist/__tests__/validator-types.test.js.map +1 -0
- package/dist/changesets.d.ts +91 -0
- package/dist/changesets.d.ts.map +1 -1
- package/dist/changesets.js +72 -2
- package/dist/changesets.js.map +1 -1
- package/dist/config.d.ts +56 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/data-accessor.d.ts +117 -0
- package/dist/data-accessor.d.ts.map +1 -1
- package/dist/data-accessor.js.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +29 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/evidence-atom-schema.d.ts +143 -8
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +258 -1
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/exit-codes.d.ts +41 -1
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +40 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/operations/tasks.d.ts +75 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/dist/task.d.ts +38 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/validator/index.d.ts +375 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +162 -0
- package/dist/validator/index.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/changesets.test.ts +176 -0
- package/src/__tests__/evidence-atom-schema.test.ts +88 -0
- package/src/__tests__/satisfies-atom.test.ts +363 -0
- package/src/__tests__/validator-types.test.ts +359 -0
- package/src/changesets.ts +93 -2
- package/src/config.ts +58 -0
- package/src/data-accessor.ts +120 -0
- package/src/dispatch/operations-registry.ts +31 -0
- package/src/evidence-atom-schema.ts +319 -8
- package/src/exit-codes.ts +42 -0
- package/src/index.ts +39 -0
- package/src/operations/tasks.ts +76 -0
- package/src/task.ts +39 -0
- package/src/validator/index.ts +385 -0
|
@@ -7629,6 +7629,37 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
7629
7629
|
] satisfies ParamDef[],
|
|
7630
7630
|
},
|
|
7631
7631
|
|
|
7632
|
+
// release query: validate-changelog (T9937 / Saga T9862 — canonical CHANGELOG
|
|
7633
|
+
// header validator that replaces the brittle inline grep step in
|
|
7634
|
+
// .github/workflows/release.yml). Read-only — exposed only under the query
|
|
7635
|
+
// gateway because no DB or filesystem mutation occurs.
|
|
7636
|
+
{
|
|
7637
|
+
gateway: 'query',
|
|
7638
|
+
domain: 'release',
|
|
7639
|
+
operation: 'validate-changelog',
|
|
7640
|
+
description:
|
|
7641
|
+
'release.validate-changelog (query) — validate that CHANGELOG.md contains the canonical `## [VERSION]` header per ADR-028 §2.5. Replaces the brittle inline grep in .github/workflows/release.yml (T9937 / Saga T9862).',
|
|
7642
|
+
tier: 1,
|
|
7643
|
+
idempotent: true,
|
|
7644
|
+
sessionRequired: false,
|
|
7645
|
+
requiredParams: ['version'],
|
|
7646
|
+
params: [
|
|
7647
|
+
{
|
|
7648
|
+
name: 'version',
|
|
7649
|
+
type: 'string',
|
|
7650
|
+
required: true,
|
|
7651
|
+
description: 'Release version (accepts v2026.5.94 or 2026.5.94)',
|
|
7652
|
+
cli: { positional: true },
|
|
7653
|
+
},
|
|
7654
|
+
{
|
|
7655
|
+
name: 'changelogPath',
|
|
7656
|
+
type: 'string',
|
|
7657
|
+
required: false,
|
|
7658
|
+
description: 'Override the CHANGELOG file path (default: <projectRoot>/CHANGELOG.md)',
|
|
7659
|
+
},
|
|
7660
|
+
] satisfies ParamDef[],
|
|
7661
|
+
},
|
|
7662
|
+
|
|
7632
7663
|
// ---------------------------------------------------------------------------
|
|
7633
7664
|
// Provenance domain — `cleo provenance` CLI surface (T9528 · Phase 2 of T9493)
|
|
7634
7665
|
// ---------------------------------------------------------------------------
|
|
@@ -195,6 +195,117 @@ export const callsiteCoverageAtomSchema = z.object({
|
|
|
195
195
|
.min(1, 'callsite-coverage atom requires a non-empty relativeSourcePath'),
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// ADR-079-r2: satisfies atom — cross-task AC binding
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Strict UUIDv4 regex (lowercase, hyphenated 8-4-4-4-12 with the `4` major
|
|
204
|
+
* version nibble and `[89ab]` variant nibble) per ADR-079-r2 §2.1 ABNF
|
|
205
|
+
* (`ac-uuid` production) and RFC 9562 §5.4. Used by the `satisfies:` atom
|
|
206
|
+
* to validate the target-AC UUID form.
|
|
207
|
+
*
|
|
208
|
+
* Validators MUST reject mixed-case to prevent silent dedupe failures — the
|
|
209
|
+
* canonical column casing is lowercase per ADR-079-r1 §2.1.
|
|
210
|
+
*
|
|
211
|
+
* @task T10506
|
|
212
|
+
* @adr ADR-079-r2 §2.1
|
|
213
|
+
*/
|
|
214
|
+
export const AC_UUID_REGEX =
|
|
215
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Positional-alias regex for `satisfies:` atom target-AC identifiers per
|
|
219
|
+
* ADR-079-r2 §2.1 ABNF (`ac-alias` production). Format: `AC<1-4 digits>`
|
|
220
|
+
* (capped at AC9999 — three orders of magnitude above realistic per-task AC
|
|
221
|
+
* counts).
|
|
222
|
+
*
|
|
223
|
+
* @task T10506
|
|
224
|
+
* @adr ADR-079-r2 §2.1
|
|
225
|
+
*/
|
|
226
|
+
export const AC_ALIAS_REGEX = /^AC[0-9]{1,4}$/;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Task-id regex for `satisfies:` atom target-task identifiers per ADR-079-r2
|
|
230
|
+
* §2.1 ABNF (`task-id` production). Format: `T<1-7 digits>` (capped at
|
|
231
|
+
* T9999999 — three orders of magnitude above current CLEO task IDs as of
|
|
232
|
+
* 2026-05-24).
|
|
233
|
+
*
|
|
234
|
+
* @task T10506
|
|
235
|
+
* @adr ADR-079-r2 §2.1
|
|
236
|
+
*/
|
|
237
|
+
export const SATISFIES_TASK_ID_REGEX = /^T[0-9]{1,7}$/;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Version-pin regex for `satisfies:` atom optional `@<ts>` suffix per
|
|
241
|
+
* ADR-079-r2 §2.1 ABNF (`version-pin` production). Format: `YYYYMMDDhhmmss`
|
|
242
|
+
* (ISO-8601-basic timestamp, 14 digits).
|
|
243
|
+
*
|
|
244
|
+
* @task T10506
|
|
245
|
+
* @adr ADR-079-r2 §2.1
|
|
246
|
+
*/
|
|
247
|
+
export const SATISFIES_VERSION_PIN_REGEX = /^[0-9]{14}$/;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* `satisfies:<task-id>#<ac-id>[@<version-pin>]` atom — cross-task AC binding
|
|
251
|
+
* per ADR-079-r2 §2.1 (full grammar) and ADR-079-r1 §2.4 (basic shape).
|
|
252
|
+
*
|
|
253
|
+
* Format:
|
|
254
|
+
*
|
|
255
|
+
* - `satisfies:T1234#a1b2c3d4-5e6f-4890-abcd-ef1234567890` — canonical
|
|
256
|
+
* UUID form (preferred for long-lived specs).
|
|
257
|
+
* - `satisfies:T1234#AC2` — positional
|
|
258
|
+
* alias form (preferred in fresh PRs where the target task's AC list
|
|
259
|
+
* is stable).
|
|
260
|
+
* - `satisfies:T1234#AC2@20260524223045` — alias +
|
|
261
|
+
* optional version-pin (Validator emissions where pin-on-mint is
|
|
262
|
+
* required by ADR-079-r2 §3.4).
|
|
263
|
+
*
|
|
264
|
+
* Exactly ONE of `targetAcId` (UUID form) or `targetAcAlias` (alias form)
|
|
265
|
+
* is populated per parsed atom. `versionPin` is always optional.
|
|
266
|
+
*
|
|
267
|
+
* ## Scope
|
|
268
|
+
*
|
|
269
|
+
* This Zod schema covers PARSING ONLY. The runtime validator semantics —
|
|
270
|
+
* the 5-check accept/reject pipeline (target exists, target not terminal,
|
|
271
|
+
* AC exists, same-saga scope rule) — ship in T10507 alongside the
|
|
272
|
+
* `evidence_satisfies_bindings` side-effect table.
|
|
273
|
+
*
|
|
274
|
+
* Per ADR-079-r2 §2.2 the maximum atom length is 120 chars; the schema does
|
|
275
|
+
* NOT enforce length here because the per-field regexes already cap the
|
|
276
|
+
* surface (`satisfies:` + 8-char T+digits + `#` + 36-char UUID + `@` +
|
|
277
|
+
* 14-char pin = 78 chars max — well under 120).
|
|
278
|
+
*
|
|
279
|
+
* @task T10506
|
|
280
|
+
* @adr ADR-079-r2 §2.1
|
|
281
|
+
* @adr ADR-079-r1 §2.4
|
|
282
|
+
*/
|
|
283
|
+
export const satisfiesAtomSchema = z.object({
|
|
284
|
+
kind: z.literal('satisfies'),
|
|
285
|
+
/** Target task ID — `T<1-7 digits>` per ADR-079-r2 §2.1. */
|
|
286
|
+
targetTaskId: z
|
|
287
|
+
.string()
|
|
288
|
+
.regex(SATISFIES_TASK_ID_REGEX, 'satisfies atom targetTaskId must match /^T[0-9]{1,7}$/'),
|
|
289
|
+
/** Lowercase UUIDv4 — populated for the canonical form; undefined for alias form. */
|
|
290
|
+
targetAcId: z
|
|
291
|
+
.string()
|
|
292
|
+
.regex(AC_UUID_REGEX, 'satisfies atom targetAcId must be a lowercase UUIDv4')
|
|
293
|
+
.optional(),
|
|
294
|
+
/** Positional alias `AC<1-4 digits>` — populated for alias form; undefined for UUID form. */
|
|
295
|
+
targetAcAlias: z
|
|
296
|
+
.string()
|
|
297
|
+
.regex(AC_ALIAS_REGEX, 'satisfies atom targetAcAlias must match /^AC[0-9]{1,4}$/')
|
|
298
|
+
.optional(),
|
|
299
|
+
/** Optional `@<14-digit YYYYMMDDhhmmss>` pin captured at mint time. */
|
|
300
|
+
versionPin: z
|
|
301
|
+
.string()
|
|
302
|
+
.regex(
|
|
303
|
+
SATISFIES_VERSION_PIN_REGEX,
|
|
304
|
+
'satisfies atom versionPin must be 14 digits (YYYYMMDDhhmmss)',
|
|
305
|
+
)
|
|
306
|
+
.optional(),
|
|
307
|
+
});
|
|
308
|
+
|
|
198
309
|
// ---------------------------------------------------------------------------
|
|
199
310
|
// Discriminated union
|
|
200
311
|
// ---------------------------------------------------------------------------
|
|
@@ -218,6 +329,8 @@ export const callsiteCoverageAtomSchema = z.object({
|
|
|
218
329
|
* - `pr` — merged-PR retroactive proof (T9764 / T9838)
|
|
219
330
|
* - `loc-drop` — engine-migration LOC reduction (T1604)
|
|
220
331
|
* - `callsite-coverage` — exported-symbol production callsite (T1605)
|
|
332
|
+
* - `satisfies` — cross-task AC binding (T10506 / ADR-079-r2;
|
|
333
|
+
* validator semantics ship in T10507)
|
|
221
334
|
*
|
|
222
335
|
* @task T10337
|
|
223
336
|
* @adr ADR-051
|
|
@@ -233,6 +346,7 @@ export const EvidenceAtomSchema = z.discriminatedUnion('kind', [
|
|
|
233
346
|
prAtomSchema,
|
|
234
347
|
locDropAtomSchema,
|
|
235
348
|
callsiteCoverageAtomSchema,
|
|
349
|
+
satisfiesAtomSchema,
|
|
236
350
|
]);
|
|
237
351
|
|
|
238
352
|
/**
|
|
@@ -332,14 +446,25 @@ export const GATE_EVIDENCE_REQUIREMENTS: Readonly<
|
|
|
332
446
|
* Result of {@link validateEvidenceForGate}.
|
|
333
447
|
*
|
|
334
448
|
* Success carries no extra payload (the caller already holds the atom list).
|
|
335
|
-
* Failure carries
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
449
|
+
* Failure carries:
|
|
450
|
+
* - {@link EvidenceValidationFailure.message} — the legacy single-line
|
|
451
|
+
* `Gate '<gate>' requires evidence: [<combo>] OR [<combo>]` format,
|
|
452
|
+
* preserved byte-for-byte for backward-compat with surfaces and tests
|
|
453
|
+
* that match against it.
|
|
454
|
+
* - {@link EvidenceValidationFailure.hint} — a richer, multi-line,
|
|
455
|
+
* example-bearing remediation hint suitable for CLI `fix:` surfaces.
|
|
456
|
+
* Added by T9949 so `E_EVIDENCE_INSUFFICIENT` errors point clearly at
|
|
457
|
+
* the alternative atom the caller needs (e.g. "this gate requires
|
|
458
|
+
* real proof — note: alone is not accepted; use commit+files,
|
|
459
|
+
* commit+note, or pr instead").
|
|
339
460
|
*/
|
|
340
|
-
export
|
|
341
|
-
|
|
342
|
-
|
|
461
|
+
export interface EvidenceValidationFailure {
|
|
462
|
+
readonly ok: false;
|
|
463
|
+
readonly message: string;
|
|
464
|
+
readonly hint: string;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export type EvidenceValidationResult = { readonly ok: true } | EvidenceValidationFailure;
|
|
343
468
|
|
|
344
469
|
/**
|
|
345
470
|
* Format the satisfying-combinations spec for a gate as the legacy
|
|
@@ -354,6 +479,115 @@ export function formatGateRequirement(gate: VerificationGate): string {
|
|
|
354
479
|
return requirement.oneOf.map((set) => `[${set.join(' AND ')}]`).join(' OR ');
|
|
355
480
|
}
|
|
356
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Example `--evidence` strings for each atom kind. Used by
|
|
484
|
+
* {@link formatGateRequirementHint} to render copy-pasteable remediation
|
|
485
|
+
* commands in CLI error surfaces.
|
|
486
|
+
*
|
|
487
|
+
* @task T9949
|
|
488
|
+
*/
|
|
489
|
+
const ATOM_EXAMPLES: Readonly<Record<EvidenceAtomKind, string>> = Object.freeze({
|
|
490
|
+
commit: 'commit:<sha>',
|
|
491
|
+
files: 'files:path/a.ts,path/b.ts',
|
|
492
|
+
'test-run': 'test-run:/tmp/vitest-out.json',
|
|
493
|
+
tool: 'tool:test',
|
|
494
|
+
url: 'url:https://example.com/docs',
|
|
495
|
+
note: 'note:<short description>',
|
|
496
|
+
decision: 'decision:D-arch-001',
|
|
497
|
+
pr: 'pr:357',
|
|
498
|
+
'loc-drop': 'loc-drop:<fromLines>:<toLines>',
|
|
499
|
+
'callsite-coverage': 'callsite-coverage:<symbolName>:<relativeSourcePath>',
|
|
500
|
+
satisfies: 'satisfies:T1234#AC2',
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Render a satisfying atom combination as a copy-pasteable `--evidence`
|
|
505
|
+
* string fragment, e.g. `['commit', 'files']` → `commit:<sha>;files:path/a.ts,path/b.ts`.
|
|
506
|
+
*
|
|
507
|
+
* @internal
|
|
508
|
+
*/
|
|
509
|
+
function renderCombinationAsEvidence(combination: ReadonlyArray<EvidenceAtomKind>): string {
|
|
510
|
+
return combination.map((kind) => ATOM_EXAMPLES[kind]).join(';');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Produce a multi-line, example-bearing remediation hint for a gate.
|
|
515
|
+
*
|
|
516
|
+
* Designed for CLI `fix:` surfaces so `E_EVIDENCE_INSUFFICIENT` errors point
|
|
517
|
+
* the caller at the specific alternative atom shape they need. The hint lists
|
|
518
|
+
* every satisfying combination as a copy-pasteable `cleo verify` invocation
|
|
519
|
+
* plus a note-specific clarification when the gate does NOT accept `note:`
|
|
520
|
+
* alone.
|
|
521
|
+
*
|
|
522
|
+
* The motivating bug (T9949) was that note-only deliverables (decision-only
|
|
523
|
+
* tasks, deletion-only refactors) hit `E_EVIDENCE_INSUFFICIENT` with the
|
|
524
|
+
* legacy single-line error and had to consult ADR-051 to figure out which
|
|
525
|
+
* additional atom kind the gate required. The richer hint inlines the answer.
|
|
526
|
+
*
|
|
527
|
+
* @param gate - Verification gate the hint is for.
|
|
528
|
+
* @returns Multi-line remediation hint suitable for an `engineError({fix:})`
|
|
529
|
+
* surface. Always ends in a clarifying sentence about whether `note:` alone
|
|
530
|
+
* is accepted for the gate.
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* ```ts
|
|
534
|
+
* formatGateRequirementHint('implemented');
|
|
535
|
+
* // "Gate 'implemented' requires programmatic evidence. Use ONE of:
|
|
536
|
+
* // - cleo verify T#### --gate implemented --evidence 'commit:<sha>;files:path/a.ts,path/b.ts'
|
|
537
|
+
* // - cleo verify T#### --gate implemented --evidence 'commit:<sha>;note:<short description>'
|
|
538
|
+
* // - cleo verify T#### --gate implemented --evidence 'decision:D-arch-001;files:path/a.ts,path/b.ts'
|
|
539
|
+
* // - cleo verify T#### --gate implemented --evidence 'decision:D-arch-001;note:<short description>'
|
|
540
|
+
* // - cleo verify T#### --gate implemented --evidence 'pr:357'
|
|
541
|
+
* // Note: 'note:' alone is NOT accepted for this gate — pair it with 'commit:' or 'decision:'."
|
|
542
|
+
* ```
|
|
543
|
+
*
|
|
544
|
+
* @task T9949
|
|
545
|
+
* @adr ADR-051 §2.3
|
|
546
|
+
*/
|
|
547
|
+
export function formatGateRequirementHint(gate: VerificationGate): string {
|
|
548
|
+
const requirement = GATE_EVIDENCE_REQUIREMENTS[gate];
|
|
549
|
+
if (!requirement) {
|
|
550
|
+
// Unknown gate — match validateEvidenceForGate's defensive default.
|
|
551
|
+
return `Gate '${gate}' has no documented evidence requirements.`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const lines: string[] = [
|
|
555
|
+
`Gate '${gate}' requires programmatic evidence. Use ONE of:`,
|
|
556
|
+
...requirement.oneOf.map(
|
|
557
|
+
(combo) =>
|
|
558
|
+
` - cleo verify T#### --gate ${gate} --evidence '${renderCombinationAsEvidence(combo)}'`,
|
|
559
|
+
),
|
|
560
|
+
];
|
|
561
|
+
|
|
562
|
+
// Note-specific clarification — T9949's headline pain point.
|
|
563
|
+
// The gate accepts note: alone IFF some single-element combination is ['note'].
|
|
564
|
+
const noteAloneAccepted = requirement.oneOf.some(
|
|
565
|
+
(combo) => combo.length === 1 && combo[0] === 'note',
|
|
566
|
+
);
|
|
567
|
+
if (noteAloneAccepted) {
|
|
568
|
+
lines.push(`Note: 'note:' alone IS accepted for this gate (waiver-style evidence).`);
|
|
569
|
+
} else {
|
|
570
|
+
// Surface every alternative pairing involving note for at-a-glance routing.
|
|
571
|
+
const notePairings = requirement.oneOf.filter(
|
|
572
|
+
(combo) => combo.length > 1 && combo.includes('note'),
|
|
573
|
+
);
|
|
574
|
+
if (notePairings.length > 0) {
|
|
575
|
+
const partners = Array.from(
|
|
576
|
+
new Set(notePairings.flatMap((combo) => combo.filter((k) => k !== 'note'))),
|
|
577
|
+
).map((k) => `'${k}:'`);
|
|
578
|
+
lines.push(
|
|
579
|
+
`Note: 'note:' alone is NOT accepted for this gate — pair it with ${partners.join(' or ')}.`,
|
|
580
|
+
);
|
|
581
|
+
} else {
|
|
582
|
+
lines.push(
|
|
583
|
+
`Note: 'note:' is NOT accepted for this gate (even when paired). See ADR-051 for the full grammar.`,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return lines.join('\n');
|
|
589
|
+
}
|
|
590
|
+
|
|
357
591
|
/**
|
|
358
592
|
* Check whether a set of parsed atoms satisfies the requirement spec for
|
|
359
593
|
* `gate`.
|
|
@@ -401,6 +635,11 @@ export function validateEvidenceForGate(
|
|
|
401
635
|
return {
|
|
402
636
|
ok: false,
|
|
403
637
|
message: `Gate '${gate}' requires evidence: ${formatGateRequirement(gate)}`,
|
|
638
|
+
// T9949: rich, example-bearing remediation hint suitable for CLI `fix:`.
|
|
639
|
+
// Always populated alongside `message` so consumers can choose between
|
|
640
|
+
// the legacy single-line surface and the multi-line hint without re-
|
|
641
|
+
// computing the spec.
|
|
642
|
+
hint: formatGateRequirementHint(gate),
|
|
404
643
|
};
|
|
405
644
|
}
|
|
406
645
|
|
|
@@ -594,6 +833,78 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
|
|
|
594
833
|
atoms.push({ kind: 'callsite-coverage', symbolName, relativeSourcePath });
|
|
595
834
|
break;
|
|
596
835
|
}
|
|
836
|
+
case 'satisfies': {
|
|
837
|
+
// ADR-079-r2 §2.1 ABNF:
|
|
838
|
+
// satisfies-atom = "satisfies:" task-id "#" ac-id [ "@" version-pin ]
|
|
839
|
+
// Payload shape: <task-id>#<ac-id>[@<version-pin>]
|
|
840
|
+
//
|
|
841
|
+
// PARSING ONLY per T10506 — runtime validator semantics (5-check
|
|
842
|
+
// accept pipeline + same-saga scope rule) ship in T10507.
|
|
843
|
+
const hashIdx = payload.indexOf('#');
|
|
844
|
+
if (hashIdx < 1 || hashIdx === payload.length - 1) {
|
|
845
|
+
throw new EvidenceParseError(
|
|
846
|
+
`Malformed satisfies atom: "${chunk}" (expected satisfies:<task-id>#<ac-id>[@<version-pin>])`,
|
|
847
|
+
'Use format: satisfies:T1234#AC2 or satisfies:T1234#<uuid> e.g. satisfies:T10506#AC1',
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
const targetTaskId = payload.slice(0, hashIdx).trim();
|
|
851
|
+
const acAndPin = payload.slice(hashIdx + 1).trim();
|
|
852
|
+
|
|
853
|
+
// task-id must match strict ABNF /^T[0-9]{1,7}$/
|
|
854
|
+
if (!SATISFIES_TASK_ID_REGEX.test(targetTaskId)) {
|
|
855
|
+
throw new EvidenceParseError(
|
|
856
|
+
`satisfies atom targetTaskId "${targetTaskId}" must match /^T[0-9]{1,7}$/ in "${chunk}"`,
|
|
857
|
+
'Use format: satisfies:T<digits>#<ac-id> e.g. satisfies:T1234#AC2',
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Split optional version-pin (after `@`)
|
|
862
|
+
const atIdx = acAndPin.indexOf('@');
|
|
863
|
+
let acIdRaw: string;
|
|
864
|
+
let versionPin: string | undefined;
|
|
865
|
+
if (atIdx === -1) {
|
|
866
|
+
acIdRaw = acAndPin;
|
|
867
|
+
versionPin = undefined;
|
|
868
|
+
} else {
|
|
869
|
+
if (atIdx < 1 || atIdx === acAndPin.length - 1) {
|
|
870
|
+
throw new EvidenceParseError(
|
|
871
|
+
`Malformed satisfies atom version-pin in "${chunk}" (expected <ac-id>@<14-digit YYYYMMDDhhmmss>)`,
|
|
872
|
+
'Use format: satisfies:T1234#AC2@20260524223045',
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
acIdRaw = acAndPin.slice(0, atIdx).trim();
|
|
876
|
+
versionPin = acAndPin.slice(atIdx + 1).trim();
|
|
877
|
+
if (!SATISFIES_VERSION_PIN_REGEX.test(versionPin)) {
|
|
878
|
+
throw new EvidenceParseError(
|
|
879
|
+
`satisfies atom versionPin "${versionPin}" must be 14 digits (YYYYMMDDhhmmss) in "${chunk}"`,
|
|
880
|
+
'Use format: satisfies:T1234#AC2@<YYYYMMDDhhmmss> e.g. satisfies:T1234#AC2@20260524223045',
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// ac-id is EITHER a strict UUIDv4 OR an AC<digits> alias — exactly one.
|
|
886
|
+
let targetAcId: string | undefined;
|
|
887
|
+
let targetAcAlias: string | undefined;
|
|
888
|
+
if (AC_UUID_REGEX.test(acIdRaw)) {
|
|
889
|
+
targetAcId = acIdRaw;
|
|
890
|
+
} else if (AC_ALIAS_REGEX.test(acIdRaw)) {
|
|
891
|
+
targetAcAlias = acIdRaw;
|
|
892
|
+
} else {
|
|
893
|
+
throw new EvidenceParseError(
|
|
894
|
+
`satisfies atom ac-id "${acIdRaw}" must be either a lowercase UUIDv4 or AC<1-4 digits> in "${chunk}"`,
|
|
895
|
+
'Use format: satisfies:T1234#AC2 (alias) or satisfies:T1234#<lowercase-uuidv4> (canonical)',
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
atoms.push({
|
|
900
|
+
kind: 'satisfies',
|
|
901
|
+
targetTaskId,
|
|
902
|
+
...(targetAcId !== undefined ? { targetAcId } : {}),
|
|
903
|
+
...(targetAcAlias !== undefined ? { targetAcAlias } : {}),
|
|
904
|
+
...(versionPin !== undefined ? { versionPin } : {}),
|
|
905
|
+
});
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
597
908
|
case 'state': {
|
|
598
909
|
// T9838: explicit-form modifier for the preceding pr: atom.
|
|
599
910
|
// Format: pr:<num>;state:MERGED. The resolver always requires
|
|
@@ -619,7 +930,7 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
|
|
|
619
930
|
default:
|
|
620
931
|
throw new EvidenceParseError(
|
|
621
932
|
`Unknown evidence kind: "${kind}" in atom "${chunk}"`,
|
|
622
|
-
'Valid kinds: commit, files, test-run, tool, url, note, loc-drop, callsite-coverage, decision, pr, state',
|
|
933
|
+
'Valid kinds: commit, files, test-run, tool, url, note, loc-drop, callsite-coverage, decision, pr, satisfies, state',
|
|
623
934
|
);
|
|
624
935
|
}
|
|
625
936
|
}
|
package/src/exit-codes.ts
CHANGED
|
@@ -224,6 +224,29 @@ export enum ExitCode {
|
|
|
224
224
|
* @task T9230
|
|
225
225
|
*/
|
|
226
226
|
LEAD_BYPASS_DETECTED = 107,
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* `cleo complete <tid>` was rejected because one or more acceptance criteria
|
|
230
|
+
* on the task have no `evidence_ac_bindings` rows pointing at them. The
|
|
231
|
+
* coverage gate is the load-bearing IVTR closure piece — every AC the task
|
|
232
|
+
* declared MUST be backed by a `direct`, `satisfies`, or `coverage` binding
|
|
233
|
+
* before completion is allowed.
|
|
234
|
+
*
|
|
235
|
+
* `error.details.unsatisfied` carries the list of offending ACs (each row
|
|
236
|
+
* has `acId`, `alias` (`AC<n>`), and `text`). Recovery options:
|
|
237
|
+
* 1. Add programmatic evidence that resolves the AC via `cleo verify` so
|
|
238
|
+
* a binding row is written.
|
|
239
|
+
* 2. Pass `--waive-ac "<csv>" --waive-reason "<text>"` on `cleo complete`
|
|
240
|
+
* to record an audited waiver (`.cleo/audit/ac-waiver.jsonl`).
|
|
241
|
+
* 3. Set `CLEO_OWNER_OVERRIDE=1` + `CLEO_OWNER_OVERRIDE_REASON=<text>` for
|
|
242
|
+
* a full bypass (logged to `.cleo/audit/force-bypass.jsonl`).
|
|
243
|
+
*
|
|
244
|
+
* @codeName E_AC_COVERAGE_INCOMPLETE
|
|
245
|
+
* @task T10509
|
|
246
|
+
* @saga T10377 (SG-IVTR-AC-BINDING)
|
|
247
|
+
* @adr ADR-079-r4
|
|
248
|
+
*/
|
|
249
|
+
AC_COVERAGE_INCOMPLETE = 108,
|
|
227
250
|
}
|
|
228
251
|
|
|
229
252
|
// ---------------------------------------------------------------------------
|
|
@@ -362,6 +385,25 @@ export const E_INVALID_SEVERITY_VALUE = 'E_INVALID_SEVERITY_VALUE' as const;
|
|
|
362
385
|
*/
|
|
363
386
|
export const E_INVALID_PIPELINE_STAGE = 'E_INVALID_PIPELINE_STAGE' as const;
|
|
364
387
|
|
|
388
|
+
/**
|
|
389
|
+
* E_AC_COVERAGE_INCOMPLETE — `cleo complete <tid>` was rejected because at
|
|
390
|
+
* least one acceptance criterion on the task has no `evidence_ac_bindings`
|
|
391
|
+
* row. The coverage gate is the load-bearing IVTR closure piece — every AC
|
|
392
|
+
* the task declared MUST be backed by a binding (`direct`, `satisfies`, or
|
|
393
|
+
* `coverage`) before completion is allowed.
|
|
394
|
+
*
|
|
395
|
+
* `error.details.unsatisfied` carries the offending ACs (each entry has
|
|
396
|
+
* `acId`, `alias`, and `text`). Recovery options live on the {@link ExitCode.AC_COVERAGE_INCOMPLETE}
|
|
397
|
+
* JSDoc.
|
|
398
|
+
*
|
|
399
|
+
* Maps to {@link ExitCode.AC_COVERAGE_INCOMPLETE} (108) at the CLI boundary.
|
|
400
|
+
*
|
|
401
|
+
* @task T10509
|
|
402
|
+
* @saga T10377 (SG-IVTR-AC-BINDING)
|
|
403
|
+
* @adr ADR-079-r4
|
|
404
|
+
*/
|
|
405
|
+
export const E_AC_COVERAGE_INCOMPLETE = 'E_AC_COVERAGE_INCOMPLETE' as const;
|
|
406
|
+
|
|
365
407
|
/** Check if an exit code represents an error (1-99). */
|
|
366
408
|
export function isErrorCode(code: ExitCode): boolean {
|
|
367
409
|
return code >= 1 && code < 100;
|
package/src/index.ts
CHANGED
|
@@ -257,6 +257,8 @@ export type {
|
|
|
257
257
|
DepsRequiredAt,
|
|
258
258
|
EnforcementProfile,
|
|
259
259
|
HierarchyConfig,
|
|
260
|
+
LeadRollupConfig,
|
|
261
|
+
LeadRollupMode,
|
|
260
262
|
LifecycleConfig,
|
|
261
263
|
LifecycleEnforcementMode,
|
|
262
264
|
LlmConfig,
|
|
@@ -288,6 +290,8 @@ export type {
|
|
|
288
290
|
export { parseClaudeCodeCredentials } from './credentials.js';
|
|
289
291
|
// === DataAccessor Interface ===
|
|
290
292
|
export type {
|
|
293
|
+
AcBindingRow,
|
|
294
|
+
AcRow,
|
|
291
295
|
ArchiveFields,
|
|
292
296
|
ArchiveFile,
|
|
293
297
|
DataAccessor,
|
|
@@ -475,10 +479,13 @@ export {
|
|
|
475
479
|
export type {
|
|
476
480
|
EvidenceAtom as EvidenceAtomInput,
|
|
477
481
|
EvidenceAtomKind,
|
|
482
|
+
EvidenceValidationFailure,
|
|
478
483
|
EvidenceValidationResult,
|
|
479
484
|
GateEvidenceRequirement,
|
|
480
485
|
} from './evidence-atom-schema.js';
|
|
481
486
|
export {
|
|
487
|
+
AC_ALIAS_REGEX,
|
|
488
|
+
AC_UUID_REGEX,
|
|
482
489
|
callsiteCoverageAtomSchema,
|
|
483
490
|
commitAtomSchema,
|
|
484
491
|
decisionAtomSchema,
|
|
@@ -486,11 +493,15 @@ export {
|
|
|
486
493
|
EvidenceParseError,
|
|
487
494
|
filesAtomSchema,
|
|
488
495
|
formatGateRequirement,
|
|
496
|
+
formatGateRequirementHint,
|
|
489
497
|
GATE_EVIDENCE_REQUIREMENTS,
|
|
490
498
|
locDropAtomSchema,
|
|
491
499
|
noteAtomSchema,
|
|
492
500
|
parseEvidenceString,
|
|
493
501
|
prAtomSchema,
|
|
502
|
+
SATISFIES_TASK_ID_REGEX,
|
|
503
|
+
SATISFIES_VERSION_PIN_REGEX,
|
|
504
|
+
satisfiesAtomSchema,
|
|
494
505
|
testRunAtomSchema,
|
|
495
506
|
toolAtomSchema,
|
|
496
507
|
urlAtomSchema,
|
|
@@ -525,6 +536,8 @@ export {
|
|
|
525
536
|
} from './evidence-record-schema.js';
|
|
526
537
|
// === Exit Codes ===
|
|
527
538
|
export {
|
|
539
|
+
// T10509 / Saga T10377 — AC-coverage gate at cleo complete (IVTR closure)
|
|
540
|
+
E_AC_COVERAGE_INCOMPLETE,
|
|
528
541
|
// T10105 / Saga T10099 — fail-loud changeset parse
|
|
529
542
|
E_CHANGESET_YAML_INVALID,
|
|
530
543
|
// SPEC-T9345 release pipeline v2 error code names (T9525)
|
|
@@ -1273,6 +1286,7 @@ export type {
|
|
|
1273
1286
|
DepGraphIssue,
|
|
1274
1287
|
DepsTreeEdge,
|
|
1275
1288
|
DepsTreeNode,
|
|
1289
|
+
TaskShowAcRowEntry,
|
|
1276
1290
|
TaskShowAttachmentEntry,
|
|
1277
1291
|
TasksAddBatchEntry,
|
|
1278
1292
|
TasksAddBatchParams,
|
|
@@ -1992,6 +2006,31 @@ export type {
|
|
|
1992
2006
|
Transport,
|
|
1993
2007
|
TransportConnectConfig,
|
|
1994
2008
|
} from './transport.js';
|
|
2009
|
+
// === Validator Role Contracts (T10510 / Saga T10377 SG-IVTR-AC-BINDING) ===
|
|
2010
|
+
// Canonical AgentRole enum + per-AC ValidatorFinding + ValidatorAttestation /
|
|
2011
|
+
// ValidatorRejection envelopes + ValidatorVerdict discriminated union. Consumed
|
|
2012
|
+
// by SDK tools (T10511) and the Max-N runtime (T10512) under Epic T10383
|
|
2013
|
+
// E-VALIDATOR-ROLE.
|
|
2014
|
+
export type {
|
|
2015
|
+
AgentRole,
|
|
2016
|
+
ValidatorAttestation,
|
|
2017
|
+
ValidatorFinding,
|
|
2018
|
+
ValidatorFindingStatus,
|
|
2019
|
+
ValidatorRejection,
|
|
2020
|
+
ValidatorVerdict,
|
|
2021
|
+
} from './validator/index.js';
|
|
2022
|
+
export {
|
|
2023
|
+
AGENT_ROLES,
|
|
2024
|
+
isAgentRole,
|
|
2025
|
+
isValidatorAttestation,
|
|
2026
|
+
isValidatorRejection,
|
|
2027
|
+
isValidatorVerdict,
|
|
2028
|
+
VALIDATOR_ID_REGEX,
|
|
2029
|
+
validatorAttestationSchema,
|
|
2030
|
+
validatorFindingSchema,
|
|
2031
|
+
validatorRejectionSchema,
|
|
2032
|
+
validatorVerdictSchema,
|
|
2033
|
+
} from './validator/index.js';
|
|
1995
2034
|
// === WarpChain Types ===
|
|
1996
2035
|
export type {
|
|
1997
2036
|
ChainShape,
|
package/src/operations/tasks.ts
CHANGED
|
@@ -138,6 +138,32 @@ export interface TasksFindParams {
|
|
|
138
138
|
* @task T9905
|
|
139
139
|
*/
|
|
140
140
|
urgent?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Filter by label — selects tasks whose `labels` array contains this
|
|
143
|
+
* value. Composes with other filters via AND.
|
|
144
|
+
*
|
|
145
|
+
* Closes GH#393 — gives `cleo find --label <name>` parity with the
|
|
146
|
+
* positional `cleo labels <name>` surface.
|
|
147
|
+
*
|
|
148
|
+
* @task T9904
|
|
149
|
+
*/
|
|
150
|
+
label?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Filter by parent task ID — restricts the result set to tasks whose
|
|
153
|
+
* `parentId` equals this value. Mirrors the `--parent` axis on
|
|
154
|
+
* `cleo list`. When the parent task is a Saga (Epic with
|
|
155
|
+
* `label='saga'`), routing goes through `task_relations.type='groups'`
|
|
156
|
+
* member IDs instead of the `parentId` column (ADR-073 §1).
|
|
157
|
+
*
|
|
158
|
+
* Composes with other filters via AND. Closes T10108 — pre-fix,
|
|
159
|
+
* `cleo find "" --parent <id>` returned every task in the project
|
|
160
|
+
* because the empty-string query bypassed all filters via
|
|
161
|
+
* `fuzzyScore('', '<title>')===80`.
|
|
162
|
+
*
|
|
163
|
+
* @task T10108
|
|
164
|
+
* @saga T9862
|
|
165
|
+
*/
|
|
166
|
+
parent?: string;
|
|
141
167
|
}
|
|
142
168
|
export type TasksFindResult = MinimalTask[];
|
|
143
169
|
|
|
@@ -211,6 +237,38 @@ export interface TasksShowResult {
|
|
|
211
237
|
* @task T9966
|
|
212
238
|
*/
|
|
213
239
|
attachments: TaskShowAttachmentEntry[];
|
|
240
|
+
/**
|
|
241
|
+
* Acceptance-criterion rows hydrated from the `task_acceptance_criteria`
|
|
242
|
+
* table (T10502). Each entry carries the stable UUID `id`, the
|
|
243
|
+
* `AC<ordinal>` alias, the ordinal itself, and the canonical AC text.
|
|
244
|
+
*
|
|
245
|
+
* Optional — undefined when the task has no rows in the table (e.g.
|
|
246
|
+
* legacy tasks not yet backfilled by T10505). Consumers should fall
|
|
247
|
+
* back to `task.acceptance` (the legacy JSON string) in that case.
|
|
248
|
+
*
|
|
249
|
+
* @task T10508
|
|
250
|
+
* @epic T10381
|
|
251
|
+
*/
|
|
252
|
+
acRows?: TaskShowAcRowEntry[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* One acceptance-criterion entry in {@link TasksShowResult.acRows}.
|
|
257
|
+
*
|
|
258
|
+
* Mirrors the `AcDetail` shape from `@cleocode/core/tasks/show.js` but
|
|
259
|
+
* lives in contracts so dispatch-layer consumers don't depend on core.
|
|
260
|
+
*
|
|
261
|
+
* @task T10508
|
|
262
|
+
*/
|
|
263
|
+
export interface TaskShowAcRowEntry {
|
|
264
|
+
/** UUIDv4 stable identifier, immutable for the AC's lifetime. */
|
|
265
|
+
id: string;
|
|
266
|
+
/** Display alias derived from ordinal — `AC1`, `AC2`, etc. */
|
|
267
|
+
alias: string;
|
|
268
|
+
/** 1-based ordinal — never reused per task (gaps remain on shrink). */
|
|
269
|
+
ordinal: number;
|
|
270
|
+
/** The AC statement text. Structured gates round-trip as JSON. */
|
|
271
|
+
text: string;
|
|
214
272
|
}
|
|
215
273
|
|
|
216
274
|
// tasks.tree dispatch params (with optional withBlockers flag — dispatch alias)
|
|
@@ -900,6 +958,24 @@ export interface TasksCompleteQueryParams {
|
|
|
900
958
|
overrideReason?: string;
|
|
901
959
|
/** Reason for acknowledging CRITICAL nexus impact risk (bypasses nexusImpact gate). */
|
|
902
960
|
acknowledgeRisk?: string;
|
|
961
|
+
/**
|
|
962
|
+
* Comma-separated AC tokens (UUIDs or `AC<n>` aliases) waived from the
|
|
963
|
+
* AC-coverage gate (T10509). MUST be paired with {@link waiveReason} —
|
|
964
|
+
* the gate rejects the call when one is set without the other.
|
|
965
|
+
*
|
|
966
|
+
* Waivers are recorded to `.cleo/audit/ac-waiver.jsonl` per ADR-079-r4 §4.
|
|
967
|
+
*
|
|
968
|
+
* @task T10509
|
|
969
|
+
* @saga T10377 (SG-IVTR-AC-BINDING)
|
|
970
|
+
*/
|
|
971
|
+
waiveAc?: string;
|
|
972
|
+
/**
|
|
973
|
+
* Justification text for the {@link waiveAc} waiver. Mandatory whenever
|
|
974
|
+
* `waiveAc` is non-empty.
|
|
975
|
+
*
|
|
976
|
+
* @task T10509
|
|
977
|
+
*/
|
|
978
|
+
waiveReason?: string;
|
|
903
979
|
}
|
|
904
980
|
/**
|
|
905
981
|
* Result of `tasks.complete` — completion confirmation with unblocked tasks.
|