/** * Helpers that compute the diff between an existing AC row set and an * incoming AC text list, then apply the dual-write (rows + history) inside * a caller-owned transaction. The legacy `tasks.acceptance` string column * MUST stay in lock-step — the legacy writer lives in addTask/updateTask; * this module only manages the row-table SSoT side. * * @adr ADR-079-r1 §2.2 — ordinal monotonicity, never reused * @epic T10381 E-AC-MIGRATION * @saga T10377 SG-IVTR-AC-BINDING * @task T10508 * @decision D013 */ import type { AcceptanceGate, AcceptanceItem, AcProjectionAuditResult, AcRow, TransactionAccessor } from '@cleocode/contracts'; /** * Coerce an {@link AcceptanceItem} into the canonical row `text` form. * * Plain strings pass through; structured {@link import('@cleocode/contracts').AcceptanceGate} * objects are JSON-serialised so the row preserves the structured payload * for future round-tripping (decision D013 § "structured-gate retention"). * * @param item — AC item from the in-memory Task.acceptance array * @returns canonical text string for the `task_acceptance_criteria.text` column */ export declare function acItemToText(item: AcceptanceItem): string; /** * Compute the row payload for a fresh task with no pre-existing AC rows. * * Generates deterministic UUID-shaped AC ids from task id + source key and * assigns 1-based ordinals matching insertion order. Returns the empty array * if the input is empty/undefined. * * @param taskId — owning task ID * @param acceptance — pipe-split AC items in display order * @returns rows ready for {@link TransactionAccessor.insertAcRows} */ export type AcInsertRow = { id: string; taskId: string; ordinal: number; text: string; kind?: 'text' | 'child_task' | 'evidence_bound'; sourceKey?: string; targetTaskId?: string | null; projection?: string; contentHash?: string | null; }; /** * Canonical text used for AC hashing/idempotency. It deliberately ignores * display-only ordering and task lifecycle fields: ordinals, titles, and * statuses never enter this value. */ export declare function canonicalizeAcText(text: string): string; /** Build a deterministic sha256 over the canonical AC representation. */ export declare function acTextHash(text: string): string; /** Default direct-text source key. Deterministic from monotonic ordinal + canonical content only. */ export declare function directTextSourceKey(ordinal: number, text: string): string; /** Stable source key for a T760 structured gate projected into an evidence-bound AC row. */ export declare function evidenceBoundSourceKey(gate: AcceptanceGate, canonicalText: string): string; /** Source key for parent-owned child projections. Excludes child title/status. */ export declare function childProjectionSourceKey(childId: string): string; /** * Deterministic UUID-shaped AC id derived only from owning task + canonical AC identity. * This is UUIDv5-shaped for ecosystem compatibility, but it is intentionally * implemented with SHA-256 so we do not introduce a new runtime dependency. */ export declare function buildAcRowId(taskId: string, canonicalIdentity: string): string; export declare function buildFreshAcRows(taskId: string, acceptance: readonly AcceptanceItem[] | undefined): AcInsertRow[]; /** Parent-owned AC projection text for a direct child task. */ export declare function buildChildProjectionAcText(childId: string, childTitle: string): string; /** Minimal direct child state needed to audit parent-owned child_task AC projections. */ export interface ChildProjectionAuditInput { readonly id: string; readonly title: string; } /** Result emitted after an idempotent child-projection rebuild attempt. */ export interface ChildProjectionRebuildResult { parentId: string; rebuilt: boolean; auditBefore: AcProjectionAuditResult; auditAfter: AcProjectionAuditResult; } /** Freshness fingerprint for one parent-owned child projection row. */ export declare function childProjectionFreshnessFingerprint(childId: string, childTitle: string): string; /** * Audit parent-owned `child_task` AC projections against current WorkGraph direct children. * * The result is intentionally typed for doctor/audit JSON output: missing rows, * extra rows, field mismatches, and stale text/fingerprint projections are all * surfaced as stable finding codes with a `dirty` flag. */ export declare function auditChildProjectionAcRows(parentId: string, children: readonly ChildProjectionAuditInput[], rows: readonly AcRow[]): AcProjectionAuditResult; /** * Plan a full parent-owned child_task projection rebuild. * * The function is idempotent: a clean audit emits a no-op plan. Dirty parents * get a delete/reinsert plan that preserves non-child AC rows, rewrites the * complete child_task set from current direct children, and records previous * child projection rows to history before deletion. */ export declare function planChildProjectionRebuild(parentId: string, children: readonly ChildProjectionAuditInput[], existing: readonly AcRow[]): { plan: AcUpdatePlan; legacyAcceptance: string[]; auditBefore: AcProjectionAuditResult; }; /** Rebuild one parent's child_task projection rows inside a caller-owned transaction. */ export declare function rebuildChildProjectionAc(tx: TransactionAccessor, parentId: string, children: readonly ChildProjectionAuditInput[], updatedAt: string): Promise; /** Returns true when an AC row is the compatibility/typed projection for `childId`. */ export declare function isChildProjectionAcRow(row: AcRow, childId: string): boolean; /** * Result of `planAcUpdate` — the row + history mutations the caller must * apply inside a transaction. */ export interface AcUpdatePlan { /** AC rows to INSERT (new, never-before-seen AC text). */ inserts: AcInsertRow[]; /** History rows to append BEFORE deleting any rows. */ history: Array<{ acId: string; previousText: string; reason: string; }>; /** * When true the caller MUST issue {@link TransactionAccessor.deleteAcRowsForTask} * BEFORE applying inserts. Used by replace-all + shrink paths to * guarantee no stale (taskId, ordinal) conflicts survive. */ fullDelete: boolean; } /** * Plan the AC table mutations for a `cleo update --acceptance` call. * * Update semantics (per ADR-079-r1 §2.2 + task spec): * - **extend** (was 2, now 3): new AC gets ordinal=3; existing 1,2 unchanged. * - **shrink** (was 3, now 1): ACs 2 and 3 → history (reason='edit'); AC 1 stays. * - **replace-all**: ALL existing rows → history; new rows inserted from ordinal=1. * * "Replace-all" is detected when ANY of the in-place ordinals would change * text — we can't tell from the input which existing AC each new AC maps * to, so any text drift implies the operator wants a wholesale rewrite. * * "Extend" is the safe path: only when the new AC list is a strict prefix * extension of the existing rows (all existing texts retained in order), * we preserve the existing rows and append the new tail with continuing * ordinals. * * "Shrink" is detected when the new list is a strict prefix of the * existing texts (length N < existing length M, and texts 1..N match). * The tail rows (N+1..M) are appended to history and then deleted. * * Any other shape (mid-edit, reorder, mixed) falls back to full * replace-all — the safest semantic for the schema's "no ordinal reuse" * invariant within a single transaction (ordinals only have to be unique * after COMMIT, so a delete-then-insert in one tx is sound). * * @param taskId — task being updated * @param existing — current AC rows from the table (ordered by ordinal ASC) * @param incoming — new AC items from `--acceptance "a|b|c"` * @returns plan to be applied in order: appendAcHistory → deleteAcRowsForTask (if fullDelete) → insertAcRows */ export declare function planAcUpdate(taskId: string, existing: readonly AcRow[], incoming: readonly AcceptanceItem[]): AcUpdatePlan; /** * Plan removal of a parent-owned child projection row while preserving the * remaining AC UUIDs, ordinals, kind/source metadata, and bindings. */ export declare function planChildProjectionRemoval(parentId: string, existing: readonly AcRow[], childId: string, reason: 'delete' | 'archive'): AcUpdatePlan; /** Remove a direct child's parent-owned AC projection from row + legacy views. */ export declare function removeChildProjectionAc(tx: TransactionAccessor, parentId: string, childId: string, reason: 'delete' | 'archive', updatedAt: string): Promise; /** * Convenience executor — apply an {@link AcUpdatePlan} against a transaction * accessor in the required order: history append → delete (if requested) → * insert. Caller MUST have already opened a transaction. * * @param tx — open transaction accessor * @param taskId — task whose AC rows are being mutated * @param plan — pre-computed plan from {@link planAcUpdate} or {@link buildFreshAcRows} */ export declare function applyAcPlan(tx: TransactionAccessor, taskId: string, plan: AcUpdatePlan): Promise; //# sourceMappingURL=ac-table.d.ts.map