/** * Runtime validator for the ADR-079-r2 `satisfies:#` evidence * atom — the 5-check pipeline shipped by T10507. * * # Why a dedicated module * * The parser for the atom (T10506) lives in * `packages/contracts/src/evidence-atom-schema.ts`. The dispatch entry * point that consumes the parsed atom lives in * `packages/core/src/tasks/evidence.ts::validateAtom`. The 5-check semantics * are non-trivial — they touch the tasks table, the AC table, the AC-history * table, and the saga membership graph — so they get their own module to * keep `evidence.ts` focused on dispatch. * * # First-failure-wins * * Per ADR-079-r2 §2.4, the 5 checks run **in order** and the validator * returns the FIRST failure with its corresponding error code. NO further * checks run once one has failed. This is critical for stability: an atom * with a malformed task-id MUST surface `E_AC_BINDING_MALFORMED`, not * `E_AC_BINDING_TARGET_NOT_FOUND`, even though both would technically * apply. * * The checks are ordered cheapest-first so a malformed atom does not * touch the DB at all and an out-of-scope atom is detected only AFTER * the cheap target+ac existence checks have passed. * * # Same-saga scope (ADR-079-r2 §2.3) * * The forward-looking `tasks.saga_id` column has not yet shipped (T10494 * owns the migration). Until it lands, same-saga membership is resolved * via the `task_relations` table where `relation_type='groups'` (the * canonical Saga-membership representation per ADR-073 §1.2 I3). The * resolution is bi-directional: a task A and a task B share a saga IFF * there exists a saga task S such that S→A and S→B both have * `relation_type='groups'` edges. As a fallback (no saga membership on * either task), A and B are considered "same scope" IFF they share the * same root epic — resolved by walking `parent_id` ancestors until a * top-level epic is reached. * * # Alias drift detection * * Per ADR-079-r2 §3, the `E_AC_ALIAS_DRIFTED` error fires when an atom * captured under an alias (`AC`) at mint time now resolves to a * different canonical UUID than what was previously persisted in the * binding side-effect table. The soft warning `W_AC_ALIAS_DRIFTED` is * surfaced (NOT yet treated as an error) when an atom-rewrite would * be required to keep the binding stable — implementation is reserved * for the AC-coverage gate (T10508 / ADR-079-r4). T10507 ships the * hard error path; the soft warning is a forward-looking placeholder. * * # Side effects * * On accept, the validator returns the canonicalised * `EvidenceAtom` with `resolvedAcUuid` populated. Writing the binding * row into `evidence_ac_bindings` is the dispatch layer's * responsibility — keeping this module side-effect-free preserves the * "validator is a pure function of (atom, state)" invariant the rest * of the evidence pipeline relies on. * * @task T10507 * @epic T10381 * @saga T10377 (SG-IVTR-AC-BINDING) * @adr ADR-079-r2 */ import type { EvidenceAtom } from '@cleocode/contracts'; /** * Parsed-atom shape consumed by {@link validateSatisfiesAtom}. * * Mirrors the `satisfies` arm of the `ParsedAtom` union exported from * `evidence.ts` — duplicated here to keep this module standalone and * avoid an import cycle through `tasks/evidence.ts`. The contract is * pinned by ADR-079-r2 §2.1 ABNF. */ export interface ParsedSatisfiesAtom { /** Atom discriminator. */ kind: 'satisfies'; /** `T<1-7 digits>` per ADR-079-r2 §2.1. */ targetTaskId: string; /** Lowercase UUIDv4/v5 — populated for canonical form. */ targetAcId?: string; /** `AC<1-4 digits>` alias — populated for alias form. */ targetAcAlias?: string; /** Optional `@YYYYMMDDhhmmss` pin captured at mint time. */ versionPin?: string; } /** * Validator outcome — mirrors `AtomValidation` from `evidence.ts` but * specialised for the satisfies atom so callers can pattern-match on * the canonical `resolvedAcUuid` populated on success. */ export type SatisfiesValidation = { ok: true; atom: Extract; } | { ok: false; reason: string; codeName: SatisfiesErrorCode; }; /** * Hard-error codes surfaced by the 5-check validator pipeline. * * Codes follow the existing `E__` convention. The first * five mirror ADR-079-r2 §3 exactly; `E_AC_ALIAS_DRIFTED` is the * sixth code that fires at validate-time when the alias resolves to * a UUID that differs from the one previously persisted under the * same `(source, target_task, alias)` triple. */ export type SatisfiesErrorCode = 'E_AC_BINDING_MALFORMED' | 'E_AC_BINDING_TARGET_NOT_FOUND' | 'E_AC_BINDING_TARGET_TERMINAL' | 'E_AC_BINDING_TARGET_AC_NOT_FOUND' | 'E_AC_BINDING_OUT_OF_SCOPE' | 'E_AC_ALIAS_DRIFTED'; /** * Validate a parsed `satisfies:#[@]` atom * against the live tasks DB. Runs the 5 checks from ADR-079-r2 §2.4 * IN ORDER, returning the FIRST failure (early return — no further * checks once one has failed). * * @param parsed - Parsed atom (post-grammar, pre-runtime check). * @param sourceTaskId - The task that bears the atom — used for the * same-saga scope check. May be omitted by callers that don't have * the task context (e.g. CLI smoke tests); when undefined, check 5 * (out-of-scope) is SKIPPED — caller is responsible for stamping * the task id before invoking `validateAtom`. * @param projectRoot - Absolute path to project root (drives tasks-db * resolution). * @returns Success carries the canonicalised * {@link EvidenceAtom['satisfies']} (with `resolvedAcUuid` populated); * failure carries the first applicable {@link SatisfiesErrorCode}. * * @adr ADR-079-r2 §2.4 (validator semantics) * @adr ADR-079-r2 §3 (error codes) * @task T10507 */ export declare function validateSatisfiesAtom(parsed: ParsedSatisfiesAtom, sourceTaskId: string | undefined, projectRoot: string): Promise; //# sourceMappingURL=satisfies-validator.d.ts.map