@cleocode/contracts 2026.5.121 → 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.
Files changed (45) hide show
  1. package/dist/__tests__/satisfies-atom.test.d.ts +24 -0
  2. package/dist/__tests__/satisfies-atom.test.d.ts.map +1 -0
  3. package/dist/__tests__/satisfies-atom.test.js +302 -0
  4. package/dist/__tests__/satisfies-atom.test.js.map +1 -0
  5. package/dist/__tests__/validator-types.test.d.ts +13 -0
  6. package/dist/__tests__/validator-types.test.d.ts.map +1 -0
  7. package/dist/__tests__/validator-types.test.js +293 -0
  8. package/dist/__tests__/validator-types.test.js.map +1 -0
  9. package/dist/config.d.ts +56 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/data-accessor.d.ts +117 -0
  12. package/dist/data-accessor.d.ts.map +1 -1
  13. package/dist/data-accessor.js.map +1 -1
  14. package/dist/evidence-atom-schema.d.ts +91 -0
  15. package/dist/evidence-atom-schema.d.ts.map +1 -1
  16. package/dist/evidence-atom-schema.js +162 -1
  17. package/dist/evidence-atom-schema.js.map +1 -1
  18. package/dist/exit-codes.d.ts +41 -1
  19. package/dist/exit-codes.d.ts.map +1 -1
  20. package/dist/exit-codes.js +40 -0
  21. package/dist/exit-codes.js.map +1 -1
  22. package/dist/index.d.ts +7 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/operations/tasks.d.ts +49 -0
  27. package/dist/operations/tasks.d.ts.map +1 -1
  28. package/dist/operations/tasks.js.map +1 -1
  29. package/dist/task.d.ts +38 -0
  30. package/dist/task.d.ts.map +1 -1
  31. package/dist/validator/index.d.ts +375 -0
  32. package/dist/validator/index.d.ts.map +1 -0
  33. package/dist/validator/index.js +162 -0
  34. package/dist/validator/index.js.map +1 -0
  35. package/package.json +2 -2
  36. package/src/__tests__/satisfies-atom.test.ts +363 -0
  37. package/src/__tests__/validator-types.test.ts +359 -0
  38. package/src/config.ts +58 -0
  39. package/src/data-accessor.ts +120 -0
  40. package/src/evidence-atom-schema.ts +188 -1
  41. package/src/exit-codes.ts +42 -0
  42. package/src/index.ts +37 -0
  43. package/src/operations/tasks.ts +50 -0
  44. package/src/task.ts +39 -0
  45. package/src/validator/index.ts +385 -0
@@ -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
  /**
@@ -383,6 +497,7 @@ const ATOM_EXAMPLES: Readonly<Record<EvidenceAtomKind, string>> = Object.freeze(
383
497
  pr: 'pr:357',
384
498
  'loc-drop': 'loc-drop:<fromLines>:<toLines>',
385
499
  'callsite-coverage': 'callsite-coverage:<symbolName>:<relativeSourcePath>',
500
+ satisfies: 'satisfies:T1234#AC2',
386
501
  });
387
502
 
388
503
  /**
@@ -718,6 +833,78 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
718
833
  atoms.push({ kind: 'callsite-coverage', symbolName, relativeSourcePath });
719
834
  break;
720
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
+ }
721
908
  case 'state': {
722
909
  // T9838: explicit-form modifier for the preceding pr: atom.
723
910
  // Format: pr:<num>;state:MERGED. The resolver always requires
@@ -743,7 +930,7 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
743
930
  default:
744
931
  throw new EvidenceParseError(
745
932
  `Unknown evidence kind: "${kind}" in atom "${chunk}"`,
746
- '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',
747
934
  );
748
935
  }
749
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,
@@ -480,6 +484,8 @@ export type {
480
484
  GateEvidenceRequirement,
481
485
  } from './evidence-atom-schema.js';
482
486
  export {
487
+ AC_ALIAS_REGEX,
488
+ AC_UUID_REGEX,
483
489
  callsiteCoverageAtomSchema,
484
490
  commitAtomSchema,
485
491
  decisionAtomSchema,
@@ -493,6 +499,9 @@ export {
493
499
  noteAtomSchema,
494
500
  parseEvidenceString,
495
501
  prAtomSchema,
502
+ SATISFIES_TASK_ID_REGEX,
503
+ SATISFIES_VERSION_PIN_REGEX,
504
+ satisfiesAtomSchema,
496
505
  testRunAtomSchema,
497
506
  toolAtomSchema,
498
507
  urlAtomSchema,
@@ -527,6 +536,8 @@ export {
527
536
  } from './evidence-record-schema.js';
528
537
  // === Exit Codes ===
529
538
  export {
539
+ // T10509 / Saga T10377 — AC-coverage gate at cleo complete (IVTR closure)
540
+ E_AC_COVERAGE_INCOMPLETE,
530
541
  // T10105 / Saga T10099 — fail-loud changeset parse
531
542
  E_CHANGESET_YAML_INVALID,
532
543
  // SPEC-T9345 release pipeline v2 error code names (T9525)
@@ -1275,6 +1286,7 @@ export type {
1275
1286
  DepGraphIssue,
1276
1287
  DepsTreeEdge,
1277
1288
  DepsTreeNode,
1289
+ TaskShowAcRowEntry,
1278
1290
  TaskShowAttachmentEntry,
1279
1291
  TasksAddBatchEntry,
1280
1292
  TasksAddBatchParams,
@@ -1994,6 +2006,31 @@ export type {
1994
2006
  Transport,
1995
2007
  TransportConnectConfig,
1996
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';
1997
2034
  // === WarpChain Types ===
1998
2035
  export type {
1999
2036
  ChainShape,
@@ -237,6 +237,38 @@ export interface TasksShowResult {
237
237
  * @task T9966
238
238
  */
239
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;
240
272
  }
241
273
 
242
274
  // tasks.tree dispatch params (with optional withBlockers flag — dispatch alias)
@@ -926,6 +958,24 @@ export interface TasksCompleteQueryParams {
926
958
  overrideReason?: string;
927
959
  /** Reason for acknowledging CRITICAL nexus impact risk (bypasses nexusImpact gate). */
928
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;
929
979
  }
930
980
  /**
931
981
  * Result of `tasks.complete` — completion confirmation with unblocked tasks.
package/src/task.ts CHANGED
@@ -306,6 +306,45 @@ export type EvidenceAtom =
306
306
  successCount: number;
307
307
  /** Total number of checks evaluated in the rollup. */
308
308
  totalChecks: number;
309
+ }
310
+ | {
311
+ /**
312
+ * Satisfies atom — cross-task acceptance-criterion binding per
313
+ * ADR-079-r2.
314
+ *
315
+ * Carries the parsed form (one of `targetAcId` UUID or
316
+ * `targetAcAlias` `AC<digits>` will be populated; optional
317
+ * `versionPin` captures the target AC's `updated_at` at mint time)
318
+ * plus the validator-canonical `resolvedAcUuid` populated by the
319
+ * T10507 5-check pipeline (target exists, not terminal, AC exists,
320
+ * same-saga scope). T10506 only ships the parser — the validated
321
+ * form is reserved here so consumers of `EvidenceAtom` (sentinel
322
+ * tables, gate-checkers, audit logs) can pattern-match on
323
+ * `kind === 'satisfies'` from day one.
324
+ *
325
+ * Format: `satisfies:<task-id>#<ac-id>[@<version-pin>]`
326
+ * - `satisfies:T1234#a1b2c3d4-5e6f-4890-abcd-ef1234567890` (UUID)
327
+ * - `satisfies:T1234#AC2` (alias)
328
+ * - `satisfies:T1234#AC2@20260524223045` (pin)
329
+ *
330
+ * @task T10506
331
+ * @adr ADR-079-r2
332
+ */
333
+ kind: 'satisfies';
334
+ /** Target task ID — `T<1-7 digits>` per ADR-079-r2 §2.1. */
335
+ targetTaskId: string;
336
+ /** Lowercase UUIDv4 — populated for canonical form; undefined for alias form. */
337
+ targetAcId?: string;
338
+ /** `AC<1-4 digits>` alias — populated for alias form; undefined for UUID form. */
339
+ targetAcAlias?: string;
340
+ /** Optional `@<YYYYMMDDhhmmss>` pin captured at mint time. */
341
+ versionPin?: string;
342
+ /**
343
+ * Canonical UUID resolved by the T10507 validator from `targetAcId`
344
+ * (already canonical) or by looking up `(targetTaskId, alias-ordinal)`.
345
+ * Reserved here for forward compatibility; undefined under T10506.
346
+ */
347
+ resolvedAcUuid?: string;
309
348
  };
310
349
 
311
350
  /**