/** * Acceptance-criteria immutability guard. * * Locks `task.acceptance` once a task enters the implementation pipeline * stage (or any later stage). Prevents the "T-THIN-WRAPPER feature-complete" * anti-pattern where acceptance criteria are silently reframed after work * has shipped to match what was actually built. * * The lock can be overridden by supplying an explicit operator `reason`, * which is appended to an append-only audit log at * `.cleo/audit/ac-changes.jsonl` together with the before/after AC, the * stage at which the override occurred, the timestamp and the agent * identifier. * * Locked stages: implementation, validation, testing, release, contribution. * * @epic T1586 Foundation Lockdown (Wave A) * @task T1590 */ import type { AcceptanceItem, Task } from '@cleocode/contracts'; /** * Pipeline stages at which acceptance criteria are considered locked. * * Once a task crosses into `implementation`, AC are treated as a binding * contract — any subsequent change must be operator-approved with a * written reason that is captured in the audit log. * * @task T1590 */ export declare const AC_LOCKED_STAGES: ReadonlySet; /** Relative path within project root for the AC-changes audit log. */ export declare const AC_CHANGES_AUDIT_FILE = ".cleo/audit/ac-changes.jsonl"; /** * One line of the AC-changes audit log. * * New records describe authorization attempts, not committed mutations. * Committed overrides live in the transactional task_updated audit details. * Historical records without status remain preserved and have unverified commit status. * * @task T1590 */ export interface AcceptanceChangeAuditEntry { /** Lifecycle meaning; absent on legacy records whose commit status is unverified. */ status?: 'authorization-attempt' | 'committed'; /** ISO-8601 timestamp at which the override was recorded. */ timestamp: string; /** Task whose acceptance criteria were modified. */ taskId: string; /** Pipeline stage of the task at the moment of override. */ stage: string; /** Operator-supplied free-text justification (required, non-empty). */ reason: string; /** Acceptance criteria before the update (deep-copied snapshot). */ oldAcceptance: AcceptanceItem[]; /** Acceptance criteria after the update (deep-copied snapshot). */ newAcceptance: AcceptanceItem[]; /** * Identity of the agent performing the override, sourced from * `CLEO_AGENT_ID` and falling back to `"cleo"` when unset. */ agent: string; /** * `append` when every existing criterion was kept byte-for-byte and new ones * were added after them (T12237, no reason needed); `override` when an * existing criterion was removed or altered under `--reason`. Absent on * records written before T12237 (all overrides). */ change?: 'append' | 'override'; } /** * Whether an acceptance update only appends: every existing criterion kept, * byte-for-byte and in place, and one or more added after them (T12237). It * makes closure strictly harder, the opposite of the reframing the lock stops. * * @param before - The criteria before the update. * @param after - The criteria the update writes. * @returns `true` for a pure append. * * @example * ```ts * isAcceptanceAppend(['A', 'B'], ['A', 'B', 'C']); // true * isAcceptanceAppend(['A', 'B'], ['A', 'C']); // false: B was removed * ``` * * @task T12237 */ export declare function isAcceptanceAppend(before: readonly AcceptanceItem[], after: readonly AcceptanceItem[]): boolean; /** * Check whether a task's pipeline stage locks acceptance-criteria changes. * * @param stage - Current pipeline stage of the task (may be null/undefined). * @returns True if the stage is in {@link AC_LOCKED_STAGES}. * * @example * ```ts * isAcceptanceLocked('research'); // => false * isAcceptanceLocked('implementation'); // => true * ``` * * @task T1590 */ export declare function isAcceptanceLocked(stage: string | null | undefined): boolean; /** * Determine whether two acceptance-criteria arrays are structurally equal. * * Used to suppress the lock when the caller passes an unchanged AC payload * (a no-op should never trip the immutability guard). * * @param a - First criteria array (or null/undefined). * @param b - Second criteria array (or null/undefined). * @returns True if both arrays have identical length and elements. * * @task T1590 */ export declare function acceptanceEquals(a: AcceptanceItem[] | null | undefined, b: AcceptanceItem[] | null | undefined): boolean; /** * Inputs to {@link enforceAcceptanceImmutability}. * * @task T1590 */ export interface EnforceAcceptanceImmutabilityOptions { /** Task being mutated; only `id`, `pipelineStage` and `acceptance` are read. */ task: Pick; /** New acceptance payload supplied by the caller (undefined → no AC change). */ newAcceptance: AcceptanceItem[] | undefined; /** Operator-supplied override reason (undefined → no override requested). */ reason: string | undefined; /** Project root used to locate the audit log directory. */ projectRoot?: string | undefined; } /** * Enforce the AC-immutability guard for a task update. * * Behaviour: * - When `newAcceptance` is `undefined`, the call is a no-op. * - When the task is not in a locked stage, the call is a no-op. * - When the new AC is structurally identical to the existing AC, the call * is a no-op (idempotent updates are always allowed). * - When the update only appends criteria ({@link isAcceptanceAppend}), it is * allowed without a reason and audited as `change: 'append'` (T12237). * - When it removes or alters an existing criterion and `reason` is missing * or empty, a {@link CleoError} with {@link ExitCode.AC_LOCKED} is thrown, * naming the criteria it would change. * - When it removes or alters one and `reason` is provided, an audit entry * (`change: 'override'`) is appended to `.cleo/audit/ac-changes.jsonl`. * * @param options - {@link EnforceAcceptanceImmutabilityOptions}. * @returns Authorization snapshot for the transaction audit, or undefined when no override applies. * @throws CleoError(AC_LOCKED) when the AC are locked and no reason is given. * * @example * ```ts * enforceAcceptanceImmutability({ * task, * newAcceptance: ['AC1', 'AC2', 'AC3'], * reason: 'operator approved scope expansion', * projectRoot: '/project', * }); * ``` * * @task T1590 */ export declare function enforceAcceptanceImmutability(options: EnforceAcceptanceImmutabilityOptions): AcceptanceChangeAuditEntry | undefined; //# sourceMappingURL=ac-immutability.d.ts.map