import { KyroCoreError } from '../core/errors'; import type { CheckResult, RemediationAnchor, ScopeVerification, SprintCloseCheckpointV1, SprintFile } from '../types'; import { type CompactScopeRemediation, type RemediationOperation, type ScopeRemediation } from './protocol'; /** * Pure remediation planner. * * Everything here is read-only: it loads a manifest and the target scope, proves that the historical * checkpoints are intact and still anchored, proves that every typed operation's precondition holds * against the *current* live state, then projects the corrected state and the record that would be * persisted. Nothing is written. `remediate apply` (T1.3) re-runs this planner under the state-writer * lock, so a defect caught here is a defect that never reaches the filesystem. */ export declare const REMEDIATION_TRANSACTION_STATUS: { /** No record and no anchor on disk — the plan has never been applied. */ readonly NOT_APPLIED: "NOT_APPLIED"; /** The immutable record exists but the live anchor does not: persistence was interrupted. */ readonly PREPARED: "PREPARED"; /** Record, anchor, commitment and live result digest all agree. */ readonly APPLIED: "APPLIED"; /** Record and anchor exist but disagree with each other or with live state. */ readonly DIVERGED: "DIVERGED"; /** The record is unreadable or fails the remediation contract. */ readonly CORRUPT: "CORRUPT"; /** The record declares a schema version this runtime cannot evaluate. */ readonly UNSUPPORTED_VERSION: "UNSUPPORTED_VERSION"; }; export type RemediationTransactionStatus = (typeof REMEDIATION_TRANSACTION_STATUS)[keyof typeof REMEDIATION_TRANSACTION_STATUS]; /** Only APPLIED is a remediation. PREPARED is an interrupted write, never a success. */ export declare function isAppliedRemediationStatus(status: RemediationTransactionStatus): boolean; export interface RemediationChange { operationId: string; kind: RemediationOperation['kind']; target: string; from: unknown; to: unknown; } export interface RemediationPlan { scope: string; remediationId: string; /** Workspace-relative path the immutable record would occupy. Reported, never written here. */ recordPath: string; sprintPath: string; record: CompactScopeRemediation; /** SHA-256 commitment that the live anchor would carry. */ commitment: string; anchor: RemediationAnchor; /** The corrected live state, including the appended anchor. Held in memory only. */ projectedSprint: SprintFile; changes: RemediationChange[]; /** State of a previously started transaction for this same remediation id. */ transactionStatus: RemediationTransactionStatus; transactionDetail: string; } export interface RemediationPlanOptions { scope: string; manifestPath: string; /** Injected so the planner stays deterministic and testable. */ now: string; kyroVersion: string; } export declare function planRemediation(options: RemediationPlanOptions): RemediationPlan; export interface ExplanationRemediationOptions { scope: string; operations: RemediationOperation[]; issues: CompactScopeRemediation['issues']; baseState: Record; liveState: Record; now: string; kyroVersion: string; reason: string; actor: string; } /** True only when the current chain head is the exact approved batch and still describes live. */ export declare function remediationBatchAlreadyApplied(scope: string, operations: RemediationOperation[], liveState: Record): boolean; /** * Plan a remediations record that *explains* live state already observed after close. * Replay must reproduce the live business digest from the close after-image. */ export declare function planExplanationRemediation(options: ExplanationRemediationOptions): RemediationPlan; /** * Classify a remediation id's on-disk transaction. Used by preview, by apply's idempotency check and * by doctor. A PREPARED result means the record was persisted but the live anchor was not — the * caller must resume or discard deliberately, never treat it as a completed remediation. */ export declare function inspectRemediationTransaction(scope: string, remediationId: string, expectedCommitment: string | null, expectedResultDigest: string | null, /** * Whether this record is the chain head. Only the head's `result` describes the CURRENT live * state; an earlier record's result is an intermediate the later ones moved past, so comparing it * against live state would report every completed chain of two or more as diverged. Intermediate * results are proven instead by the replay in resolveRemediationRebase. */ isChainHead?: boolean): { status: RemediationTransactionStatus; detail: string; }; /** * Whether a LATER sprint has started since the newest close checkpoint. * * Everything that compares live state against a frozen image — the checkpoint after-image, or a * remediation record's result digest — is only meaningful until that happens. Once sprint N+1 is * under way, its ordinary edits move live state off those images by design, and reading that as * tampering turns normal in-sprint work into a DIVERGED failure. Doctor's checkpoint lens already * drew this line; this helper exists so the remediation and verification lenses draw the same one. */ export declare function isSupersededByActiveSprint(scope: string, state?: Record | null): boolean; /** Doctor lens over a scope's remediation chain. Read-only; never repairs. */ export declare function inspectRemediationChain(scope: string): CheckResult[]; /** What a remediation record that no live anchor references means for the scope. */ export interface UnanchoredRemediationFinding { id: string; status: RemediationTransactionStatus; detail: string; remedy: string; } /** * Records on disk that no live anchor references. * * The chain walk is driven by anchors, so a record whose anchor was never written is invisible to * it — and that is exactly the state an interrupted publish leaves behind, as well as the state a * planted record imitates. Both must be named, and differently: an interrupted publish continues the * chain and is resumable by re-running the same manifest, while a record that does not continue the * chain was not produced by this scope's transaction and must never be resumed into it. * * This is the SEMANTIC evaluation, deliberately separate from how doctor renders it. Doctor's chain * lens and the verification-state derivation both call this one function, because a reader that * detected a planted record and a reader that reported the scope healthy were describing the same * archive and could not both be right. */ export declare function evaluateUnanchoredRemediationRecords(scope: string, anchors: RemediationAnchor[], headCommitment?: string | null, headResult?: string | null): UnanchoredRemediationFinding[]; /** * Continuity is checked on BOTH links. The commitment chain proves ordering; the state chain proves * each record starts where the previous one ended. Without the second, an intermediate record could * carry a result digest describing a state nothing ever produced and still be reported APPLIED here, * because only the head is compared against live state. * * Shared by doctor's per-record inspection and the scope verification derivation so the two surfaces * cannot disagree about a forked, reordered, or gapped chain (T2.1 finding 3). */ export declare function chainContinuityIssue(record: ScopeRemediation | null, expectedHead: string | null, expectedBase: string | null): string | null; /** * Business-state digest of any parsed scope state: the remediations anchor is excluded. * * Comparing this against a checkpoint's images trusts nothing about the chain's contents — it only * removes the anchor key itself from the comparison, so merely recording a remediation cannot make * an otherwise-identical state look like drift. */ export declare function businessStateDigest(value: unknown): string | null; export type RemediationRebase = /** No remediation chain: the closed-state comparison stands on its own. */ { kind: 'none'; } /** Live state is the closed state plus an audited, fully applied chain. */ | { kind: 'remediated'; through: string; } /** A chain exists but does not explain the live state; the caller must keep reporting divergence. */ | { kind: 'broken'; }; export type RemediationReplayState = { kind: 'none'; state: Record; headCommitment: null; through: null; } | { kind: 'remediated'; state: Record; headCommitment: string; through: string; } | { kind: 'broken'; detail: string; }; /** * Explain a live state that matches neither side of a close checkpoint. * * A remediated scope is *supposed* to differ from its checkpoint — that is the whole point of an * append-only correction. But "there is a remediation chain" must never be enough to bless drift, * or the check that detects a post-close edit becomes the mechanism that certifies one. * * The chain is replayed: for the first record, replay from checkpoint and verify the result. * Compact v2 records advance that state directly through their typed operations; historic v1 * records retain their versioned snapshot witness only when a later link needs that exact image. * The first record is allowed to have begun from a different base (e.g., a corruption). * The full chain is proven correct if the final result matches the live state. */ export declare function resolveRemediationRebase(scope: string, closedState: unknown): RemediationRebase; /** * Replay the anchored remediation chain without requiring its head to equal current live state. * Integrity prepare uses this state as the baseline for a later, legitimate post-close delta. */ export declare function resolveRemediationReplayState(scope: string, closedState: unknown): RemediationReplayState; /** A valid close checkpoint located by the shared scan, newest first. */ export interface ValidCloseCheckpoint { path: string; checkpoint: SprintCloseCheckpointV1; } /** * Newest close wins: sprintN desc, then createdAt desc, then checkpointId desc. The single shared * recency definition used by doctor and by the scope verification derivation. */ export declare function compareCheckpointRecency(left: SprintCloseCheckpointV1, right: SprintCloseCheckpointV1): number; /** Every valid close checkpoint for a scope, newest first. One shared scan for doctor and status. */ export declare function listValidCloseCheckpoints(scope: string): ValidCloseCheckpoint[]; /** The most recent valid close checkpoint for a scope, or null when none exists. */ export declare function latestValidCloseCheckpoint(scope: string): SprintCloseCheckpointV1 | null; export declare function latestValidCloseCheckpointEntry(scope: string): ValidCloseCheckpoint | null; /** * Derive a scope's single named verification state. Doctor and status both call THIS function and * report the same state and the same detail string — a second derivation is the defect this task * exists to remove (ADR-0002). * * Precedence is fail-closed: diverged > unsupported > remediated > recertified > historical. * - Unreadable or contract-invalid records behind a well-formed anchor are diverged. * - An unknown schemaVersion with otherwise intact digests is unsupported. * - A present, healthy chain is a recorded correction: per S5 the scope is remediated, never * historical, even when the correction net-restored the checkpoint after-image (T2.1 finding 2). * - Live state equal to the checkpoint after-image is historical only when no chain exists. * - Real drift is remediated only when the replayed chain reproduces the live state exactly; drift * that does not replay is diverged. * * The chain is walked with the SAME per-record transaction statuses and continuity links doctor * uses (inspectRemediationChain / chainContinuityIssue), so the two surfaces cannot disagree about * a forked, reordered, or gapped chain (T2.1 finding 3). */ export declare function deriveScopeVerificationState(scope: string): ScopeVerification | null; /** * Apply a batch's operations IN ORDER, evaluating each precondition against the state as it stands * at that point in the batch. * * Planning and replay share this one implementation on purpose. When they were separate, the planner * checked every precondition against the original state while the replay checked them sequentially, * so a batch touching the same field twice was accepted at apply time and then failed its own * verification — an honestly applied remediation that doctor reported as tampering. One executor * makes that class of disagreement impossible by construction. */ export declare function executeRemediationOperations(state: Record, operations: RemediationOperation[], skipPreconditions?: boolean): { state: Record; changes: RemediationChange[]; } | { failure: KyroCoreError; }; export declare function remediationsDir(scope: string): string; export declare function remediationRecordPath(scope: string, remediationId: string): string; export declare function remediationRecordExists(scope: string, remediationId: string): boolean; //# sourceMappingURL=plan.d.ts.map