/** * Helpers that compute the diff between an existing AC row set and an * incoming mixed acceptance list, then apply row/history mutations inside * a caller-owned transaction. Parent projection mutations also preserve the * canonical typed gates in the JSON compatibility view; literal strings * never gain gate semantics merely because their bytes resemble JSON. * * @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'; import { acTextHash, buildAcRowId, canonicalizeAcText } from './ac-identity.js'; export { acTextHash, buildAcRowId, canonicalizeAcText }; /** * 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; /** Row uid to keep (T12341); omitted → the writer mints a new one. */ uid?: string | null; /** Birth fingerprint to keep with the uid (T12341): write-once, never re-derived. */ birthFp?: string | null; }; /** 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; 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: AcceptanceItem[]; 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; /** * Which existing criterion each incoming AC continues, as uids (T12341): an * incoming AC with the same text as an existing one keeps that one's uid (a * reorder keeps identity); the rest take the uid of the unclaimed existing AC * at the same position (an in-place edit keeps identity, so its evidence * bindings follow it); anything left gets `null` and a new uid. * * @param existing - Current AC rows, ordered by ordinal. * @param incomingTexts - Incoming AC texts, in their new order. * @returns One uid (or `null`) per incoming AC. */ export declare function carryAcUids(existing: readonly AcRow[], incomingTexts: readonly string[]): Array; /** * 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; acUid?: string | null; }>; /** * When true `inserts` is the task's COMPLETE AC set: {@link applyAcPlan} * deletes rows whose id is absent from it, updates kept ids in place and * inserts the rest (a diff, never delete-all-then-reinsert — T12789, so * evidence bindings on surviving ACs are never cascade-deleted). Used by * replace-all, shrink and child-projection rebuild/removal paths. When * false `inserts` are appended as new rows. */ 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 with {@link applyAcPlan} */ export declare function planAcUpdate(taskId: string, existing: readonly AcRow[], incoming: readonly AcceptanceItem[]): AcUpdatePlan; /** * Plan replacing the typed gate at acceptance `index` in place (T12988, * `cleo req replace`): every other row is kept exactly; the target row keeps * its id, ordinal and uid (a REQ-ID row's id derives from the REQ-ID, so its * evidence bindings survive and go stale on the new text) and only its text * changes. The superseded text is kept as one history row, reason `replace`. * * @param taskId - Task whose row is replaced. * @param existing - Current AC rows. * @param index - Zero-based acceptance index of the gate. * @param gate - The replacement gate (same REQ-ID). * @returns A full-set plan for {@link applyAcPlan}. * @throws CleoError when the rows do not project the acceptance array, or the * replacement would change the row's identity. * @task T12988 */ export declare function replaceAcRowPlan(taskId: string, existing: readonly AcRow[], index: number, gate: AcceptanceGate): 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; /** * The subset of {@link TransactionAccessor} that {@link applyAcPlan} writes * through — any open transaction accessor satisfies it. * * @task T12789 */ export type AcPlanWriter = Pick; /** * Convenience executor — apply an {@link AcUpdatePlan} against a transaction * accessor in the required order: history append → (for `fullDelete`) delete * the ids that leave the set, update kept ids in place → insert new ids. * 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: AcPlanWriter, taskId: string, plan: AcUpdatePlan): Promise; //# sourceMappingURL=ac-table.d.ts.map