/** * task-runtime — canonical persistence layer for `aiws change tasks` (G-026 M1). * * Implements ADR-0004 sub-decisions #10/#12/#17: * - journal-first writes: events.jsonl (source of truth) → snapshot → tasks.jsonl projection * - restart reconciliation (no blind re-dispatch) * - per-change exclusive lock with owner proof * - legacy-import read-only compatibility layer * * File layout (per change): * .aiws/changes//tasks/ * runtime.json index snapshot (derived, not truth) * runtime.lock/ exclusive lock (owner=pid+nonce) * incidents// cleanup incidents (first-class entities) * /attempts// * attempt.json static attempt metadata * lease.json resource lease (registered before resource creation) * events.jsonl append-only lifecycle journal (source of truth) * observations.jsonl worker observations * attempt-result.json completion contract (structured exit record) * done.signal result-readable marker (NOT a success proof) * change_manifest.json workspace manifest snapshot * artifacts/ attempt-scoped artifacts * * Legacy layout (read-only compatibility): * tasks/tasks.jsonl * tasks//evidence/done.signal * tasks//evidence/summary.md */ export declare const RUNTIME_SCHEMA_VERSION = 1; /** Three-dimensional attempt state (ADR-0004 #4). */ export type AttemptPhase = "admitted" | "launching" | "ready" | "running" | "execution_terminal" | "awaiting_cleanup" | "terminal"; export type AttemptResultStatus = "unknown" | "passed" | "execution_failed" | "timeout" | "stalled" | "cancelled" | "spawn_failed" | "invalid_completion" | "scope_violation" | "policy_violation" | "fatal"; export type ResourceState = "not_created" | "active" | "cleanup_confirmed" | "unknown"; /** Producer of an attempt (ADR-0004 #14/#17). */ export type AttemptProducer = "worker" | "runtime" | "orchestrator" | "legacy-import"; export interface AttemptMeta { attemptId: string; taskId: string; changeId: string; attemptNo: number; retryCount: number; producer: AttemptProducer; trustLevel: "full" | "compatibility"; needsVerification: boolean; phase: AttemptPhase; resultStatus: AttemptResultStatus; resourceState: ResourceState; createdAt: string; updatedAt: string; } /** Immutable resource lease registered BEFORE resource creation (ADR-0004 #5). */ export interface ResourceLease { schemaVersion: number; attemptId: string; taskId: string; changeId: string; nonce: string; backend: "detached-tmux" | "split-pane" | "l1" | "unknown"; cwd: string; /** Process identity, if known at registration time. */ pid?: number; pgid?: number; /** tmux identity for split-pane: real host session + worker pane only. */ hostSessionName?: string; windowId?: string; paneId?: string; sessionName?: string; commandFingerprint?: string; createdAt: string; cleanupDeadlineMs: number; cleanupStrategy: { gracefulInterruptMs: number; forceProcessTerminateWaitMs: number; backendResourceKillWaitMs: number; resourceVerificationRetries: number; }; } /** One lifecycle event appended to events.jsonl (journal-first, source of truth). */ export interface RuntimeEvent { seq: number; at: string; type: string; changeId: string; taskId?: string; attemptId?: string; incidentId?: string; data?: Record; } /** First-class cleanup incident (ADR-0004 #7). */ export interface CleanupIncident { incidentId: string; changeId: string; attemptId?: string; taskId?: string; kind: "cleanup_unconfirmed" | "unowned_resource" | "unowned_workspace_change" | "reconciliation_unknown" | "schema_unknown" | "journal_corrupt"; reason: string; status: "open" | "resolved"; createdAt: string; resolvedAt?: string; resolution?: string; } /** Versioned runtime snapshot (index, NOT truth). */ export interface RuntimeSnapshot { schemaVersion: number; changeId: string; updatedAt: string; tasks: Record; attempts: Record; incidents: Record; journalLastSeq: number; } /** Fail-closed error: unknown schema / corrupt journal / illegal transition. */ export declare class RuntimeCorruptError extends Error { constructor(message: string); } /** Write JSON atomically: tmp file → fsync → rename (crash-safe). */ export declare function atomicWriteJson(file: string, value: unknown): Promise; /** Append one line to a JSONL file with flush+fsync. */ export declare function appendJsonl(eventsDir: string, fileName: string, record: unknown): Promise; export declare class TaskRuntimeLayout { readonly changeDir: string; readonly tasksDir: string; readonly runtimeJson: string; readonly lockDir: string; readonly incidentsDir: string; constructor(changeDir: string); attemptDir(taskId: string, attemptId: string): string; attemptFile(taskId: string, attemptId: string, name: string): string; incidentDir(incidentId: string): string; incidentFile(incidentId: string, name: string): string; legacyTasksJsonl(): string; legacyTaskEvidenceDir(taskId: string): string; } export interface LayoutStatus { kind: "new" | "legacy" | "mixed" | "absent"; hasRuntimeJson: boolean; hasEvents: boolean; hasLegacyTasksJsonl: boolean; hasLegacyDoneSignals: boolean; hasNewAttemptDirs: boolean; } /** Probe a change's tasks dir to decide new/legacy handling (new never writes legacy). */ export declare function probeLayout(layout: TaskRuntimeLayout): Promise; export declare function newAttemptId(): string; export declare function newIncidentId(): string; export declare function newNonce(): string;