/** * Atomicity enforcement for thin-agent / ORC-006 compliance. * * Workers are defined as atomic if they touch ≤ {@link MAX_WORKER_FILES} files * with an explicit AC.files declaration. This prevents workers from * accidentally becoming sprawling lead-scope work and keeps audit trails * reviewable. * * Orchestrator and lead roles are not subject to this gate — they are * explicitly allowed broader scope by design. * * @task T889 Orchestration Coherence v3 * @task T894 Atomicity guard (W3-3) * @task T9214 Atomicity gate UX hardening (W4) */ import type { AgentSpawnCapability } from '@cleocode/contracts'; import { ExitCode } from '@cleocode/contracts'; /** * Maximum number of files a worker-role task may declare before it must be * split into subtasks with each child owning at most this many files. */ export declare const MAX_WORKER_FILES = 3; /** * Stable LAFS error codes emitted by {@link checkAtomicity}. * * - `E_ATOMICITY_VIOLATION` — worker declared more than {@link MAX_WORKER_FILES} * files. * - `E_ATOMICITY_NO_SCOPE` — worker did not declare any files (missing * AC.files). */ export type AtomicityErrorCode = 'E_ATOMICITY_VIOLATION' | 'E_ATOMICITY_NO_SCOPE'; /** * Structured waiver reason recorded when an orchestrator-tier caller defers * scope declaration to the spawned worker. * * Only tier-1+ orchestrators may pass this waiver. Tier-0 (direct user) callers * MUST still declare files explicitly — widening the waiver to tier-0 is * explicitly out of scope per T9214 constraints. */ export type AtomicityWaiverReason = 'orchestrator-scope-tier1-call'; /** * Input shape for {@link checkAtomicity}. * * Callers may provide the declared files via either {@link declaredFiles} or * the convenience alias {@link acFiles}. When both are present, `declaredFiles` * wins. */ export interface AtomicityInput { /** Task id being checked (used in error messages). */ taskId: string; /** Agent role this task is about to be dispatched to. */ role: AgentSpawnCapability; /** Free-text acceptance criteria lines (not parsed by this check). */ acceptance?: readonly string[]; /** Explicit files scope (canonical field). */ declaredFiles?: readonly string[]; /** Alias for {@link declaredFiles} — matches AC.files casing. */ acFiles?: readonly string[]; /** * Structured waiver that allows a tier-1+ orchestrator to defer file-scope * declaration to the spawned worker at commit time. * * Setting this to `'orchestrator-defer'` bypasses `E_ATOMICITY_NO_SCOPE` * for the child task and records `atomicity_waiver: * 'orchestrator-scope-tier1-call'` in the returned result so the caller * can emit an auditable manifest entry. * * IMPORTANT: This waiver MUST NOT be used by tier-0 (direct user) callers * — only by orchestrators that are explicitly delegating scope accountability * to the worker. The waiver does NOT suppress `E_ATOMICITY_VIOLATION` (file- * count overflow). * * @task T9214 — orchestrator-defer UX hardening */ scope?: 'orchestrator-defer'; } /** * Result of {@link checkAtomicity}. * * On success, only `allowed` and `meta` are populated. On failure, `code`, * `message`, and `fixHint` describe the rejection and how to remedy it. * When a waiver is granted, `atomicity_waiver` is set so callers can emit * an auditable manifest entry. */ export interface AtomicityResult { /** Whether the spawn is permitted under the atomicity rule. */ allowed: boolean; /** Error code when `allowed === false`. */ code?: AtomicityErrorCode; /** Human-readable message when `allowed === false`. */ message?: string; /** Suggested fix when `allowed === false`. */ fixHint?: string; /** * Structured waiver reason recorded when an orchestrator uses * `scope: 'orchestrator-defer'` to bypass `E_ATOMICITY_NO_SCOPE`. * * Callers MUST persist this value in the spawn manifest entry so the * bypass is auditable. * * @task T9214 */ atomicity_waiver?: AtomicityWaiverReason; /** Counts observed during the check. */ meta?: { /** Number of declared files (AC.files) observed. */ fileCount: number; /** Whether an explicit file-scope declaration was present. */ hasScope: boolean; }; } /** * Check that a task about to be spawned respects the atomicity rule for its * declared role. * * Only the `worker` role is gated. `orchestrator` and `lead` roles pass * through unconditionally — they are permitted broader scope by design and * coordinate atomic workers downstream. * * Worker tasks MUST: * 1. Declare at least one file via AC.files (missing scope → `E_ATOMICITY_NO_SCOPE`). * 2. Declare no more than {@link MAX_WORKER_FILES} files (overflow → * `E_ATOMICITY_VIOLATION`). * * Orchestrators (tier-1+) may set `input.scope = 'orchestrator-defer'` to let * the worker declare its own scope at commit time. This produces * `allowed: true` with `atomicity_waiver: 'orchestrator-scope-tier1-call'` in * the result — callers MUST persist the waiver in the spawn manifest. * * @param input - Task metadata and declared role. * @returns Atomicity decision plus diagnostics. * * @example * ```typescript * // Standard path — worker declares files. * const result = checkAtomicity({ * taskId: 'T1001', * role: 'worker', * declaredFiles: ['packages/core/src/foo.ts'], * }); * if (!result.allowed) { * throw new AtomicityViolationError(result); * } * ``` * * @example * ```typescript * // Orchestrator-defer waiver — scope accountability delegated to worker. * const result = checkAtomicity({ * taskId: 'T1002', * role: 'worker', * scope: 'orchestrator-defer', * }); * // result.allowed === true * // result.atomicity_waiver === 'orchestrator-scope-tier1-call' * // Caller MUST record atomicity_waiver in spawn manifest. * ``` */ export declare function checkAtomicity(input: AtomicityInput): AtomicityResult; /** * Thrown when {@link checkAtomicity} rejects a spawn. * * Carries a stable LAFS `code` and numeric `exitCode` aligned with * {@link ExitCode.ATOMICITY_VIOLATION} so CLI callers can surface exit 69 * without additional mapping. * * @example * ```typescript * const result = checkAtomicity(input); * if (!result.allowed) { * throw new AtomicityViolationError(result); * } * ``` */ export declare class AtomicityViolationError extends Error { /** Stable LAFS error code string for envelope emission. */ readonly code: AtomicityErrorCode; /** Numeric exit code aligned with {@link ExitCode.ATOMICITY_VIOLATION}. */ readonly exitCode: ExitCode; /** Diagnostic metadata from the originating check. */ readonly meta: AtomicityResult['meta']; /** Suggested fix extracted from the originating check. */ readonly fixHint: string | undefined; /** * @param result - The rejection result returned by {@link checkAtomicity}. * @throws {TypeError} if called with an `allowed: true` result. */ constructor(result: AtomicityResult); } //# sourceMappingURL=atomicity.d.ts.map