"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.contractRevisionPlans = exports.LifecycleRunContractRevisionError = exports.LIFECYCLE_CONTRACT_REVISION_ROLLBACK_GATES = exports.SHA256_PATTERN = exports.RUN_DIRECTORY_PATTERN = exports.TOKEN_PATTERN = exports.MAX_RECEIPT_IDS = exports.MAX_STRUCTURED_FILES = exports.MAX_CONTRACT_REVISIONS = exports.MAX_ENTRIES = exports.LIFECYCLE_METADATA_POSTURES = exports.MAX_LIFECYCLE_RUN_RESULT_BYTES = exports.MAX_LIFECYCLE_RUN_MANIFEST_BYTES = exports.LIFECYCLE_RUN_MANIFEST_NAME = exports.LIFECYCLE_RUN_CONTEXT_SCHEMA_VERSION = void 0;
exports.projectLifecycleRunCaptureEvidencePath = projectLifecycleRunCaptureEvidencePath;
exports.prepareLifecycleRunCapture = prepareLifecycleRunCapture;
exports.captureLifecycleRunEnvelope = captureLifecycleRunEnvelope;
exports.isNoWriteChangedPathsParserRejection = isNoWriteChangedPathsParserRejection;
exports.openLifecycleRunContext = openLifecycleRunContext;
exports.recordLifecycleRunOutcome = recordLifecycleRunOutcome;
// Lifecycle run-context contracts, capture, inspection, and persistence orchestration.
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../errors");
const sandbox_contract_1 = require("../paths/sandbox-contract");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const evidence_store_1 = require("./evidence-store");
const context_inspection_1 = require("./context-inspection");
const context_storage_1 = require("./context-storage");
exports.LIFECYCLE_RUN_CONTEXT_SCHEMA_VERSION = 1;
exports.LIFECYCLE_RUN_MANIFEST_NAME = 'run-context.json';
exports.MAX_LIFECYCLE_RUN_MANIFEST_BYTES = 512 * 1024;
exports.MAX_LIFECYCLE_RUN_RESULT_BYTES = 1024 * 1024;
exports.LIFECYCLE_METADATA_POSTURES = ['read-only', 'sync-on-clean-review'];
exports.MAX_ENTRIES = 512;
exports.MAX_CONTRACT_REVISIONS = 128;
exports.MAX_STRUCTURED_FILES = 2048;
exports.MAX_RECEIPT_IDS = 64;
exports.TOKEN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/;
exports.RUN_DIRECTORY_PATTERN = sandbox_contract_1.LIFECYCLE_RUN_DIRECTORY_PATH_PATTERN;
exports.SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
exports.LIFECYCLE_CONTRACT_REVISION_ROLLBACK_GATES = [
    'phase-authoring-review',
    'milestone-scaffold',
    'task-readiness',
];
class LifecycleRunContractRevisionError extends Error {
    code;
    constructor(code, message) {
        super(message);
        this.name = 'LifecycleRunContractRevisionError';
        this.code = code;
    }
}
exports.LifecycleRunContractRevisionError = LifecycleRunContractRevisionError;
exports.contractRevisionPlans = new WeakMap();
function projectLifecycleRunCaptureEvidencePath(binding, status) {
    const sequenceLabel = String(binding.nextSequence).padStart(4, '0');
    const filename = `${sequenceLabel}-${(0, context_storage_1.slug)(binding.gate)}-${(0, context_storage_1.slug)(binding.commandIdentity)}-${status}.json`;
    return (0, sandbox_contract_1.assertLifecycleRunContentPath)(node_path_1.default.posix.join(binding.runDirectory, 'evidence', filename));
}
function prepareLifecycleRunCapture(options) {
    const repositoryRoot = (0, context_storage_1.canonicalRoot)(options.repositoryRoot);
    const inspection = (0, context_inspection_1.inspectLifecycleRunContext)({
        repositoryRoot,
        runDirectory: options.runDirectory,
    });
    if (inspection.state !== 'fresh') {
        throw (0, errors_1.createUsageError)(`Lifecycle run '${inspection.manifest.runDirectory}' must be fresh before binding a command.`, [
            `Observed state: ${inspection.state}`,
            ...inspection.staleReasons,
            ...inspection.incompleteReasons,
            'Use resume run status to inspect the current no-write evidence before continuing.',
        ]);
    }
    const commandIdentity = (0, context_storage_1.requireBoundedText)(options.commandIdentity, 'Command identity');
    if (commandIdentity === 'day-shift' || commandIdentity.startsWith('resume run ')) {
        throw (0, errors_1.createUsageError)('Lifecycle run binding requires one ordinary registered Day Shift command.', [
            'Use resume run commands directly without --run-directory/--run-gate global binding.',
        ]);
    }
    const artifact = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, options.artifactPath ?? inspection.manifest.activeArtifactPath, 'Lifecycle run artifact');
    const gate = (0, context_storage_1.requireToken)(options.gate, 'Lifecycle run gate');
    (0, context_storage_1.assertEvidenceDirectorySafe)(repositoryRoot, inspection.manifest.runDirectory);
    return {
        repositoryRoot,
        runDirectory: inspection.manifest.runDirectory,
        manifestPath: inspection.manifestPath,
        manifestHash: inspection.manifestHash,
        runId: inspection.manifest.runId,
        nextSequence: inspection.manifest.nextSequence,
        commandIdentity,
        artifactPath: artifact.relativePath,
        artifactHash: artifact.hash,
        activeArtifactPath: inspection.manifest.activeArtifactPath,
        activeArtifactHash: inspection.manifest.activeArtifactHash,
        gate,
    };
}
function captureLifecycleRunEnvelope(options) {
    const binding = options.binding;
    const repositoryRoot = (0, context_storage_1.canonicalRoot)(binding.repositoryRoot);
    const loaded = (0, context_inspection_1.readManifest)(repositoryRoot, binding.runDirectory);
    const manifest = loaded.manifest;
    if (loaded.manifestHash !== binding.manifestHash ||
        manifest.runId !== binding.runId ||
        manifest.nextSequence !== binding.nextSequence) {
        throw (0, errors_1.createUsageError)('Lifecycle run manifest changed after command binding and before evidence capture.', [
            'The underlying command outcome is final and must not be rerun automatically.',
            'Preserve the returned envelope and inspect resume run status before importing recovery evidence.',
        ]);
    }
    if (manifest.entries.length >= exports.MAX_ENTRIES) {
        throw (0, errors_1.createUsageError)(`Lifecycle run contexts may contain at most ${exports.MAX_ENTRIES} entries.`);
    }
    const evidenceBytes = Buffer.from(options.envelopeText, 'utf8');
    if (evidenceBytes.byteLength === 0 || evidenceBytes.byteLength > exports.MAX_LIFECYCLE_RUN_RESULT_BYTES) {
        throw (0, errors_1.createUsageError)(`Lifecycle structured evidence must contain 1-${exports.MAX_LIFECYCLE_RUN_RESULT_BYTES} bytes.`);
    }
    const envelope = (0, context_storage_1.parseObjectJson)(evidenceBytes, 'Lifecycle output envelope');
    if (envelope.status !== 'ok' && envelope.status !== 'error') {
        throw (0, errors_1.createUsageError)("Lifecycle output envelope requires status 'ok' or 'error'.");
    }
    if (envelope.command !== binding.commandIdentity) {
        throw (0, errors_1.createUsageError)(`Lifecycle output command '${String(envelope.command)}' does not match bound command '${binding.commandIdentity}'.`);
    }
    const artifact = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, binding.artifactPath, 'Lifecycle run artifact');
    const phase = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, manifest.phasePath, 'Selected phase');
    const recordedAt = (0, context_storage_1.requireTimestamp)(options.recordedAt ?? new Date().toISOString(), 'Outcome capture time');
    const status = envelope.status;
    const sequence = manifest.nextSequence;
    const sequenceLabel = String(sequence).padStart(4, '0');
    const evidencePath = projectLifecycleRunCaptureEvidencePath(binding, status);
    const evidenceAbsolute = node_path_1.default.join(repositoryRoot, evidencePath);
    if (node_fs_1.default.existsSync(evidenceAbsolute)) {
        throw (0, errors_1.createUsageError)(`Deterministic lifecycle evidence path '${evidencePath}' already exists.`);
    }
    const error = (0, context_storage_1.asRecord)(envelope.error);
    const data = (0, context_storage_1.asRecord)(envelope.data);
    const writeOutcome = (0, context_storage_1.requireBoundedText)(typeof error?.writeOutcome === 'string'
        ? error.writeOutcome
        : typeof data?.writeOutcome === 'string'
            ? data.writeOutcome
            : status === 'ok'
                ? 'complete'
                : 'no-writes', 'Write outcome');
    const receiptIds = (0, context_storage_1.normalizeReceiptIds)((0, context_storage_1.collectReceiptIds)(envelope));
    const entryId = `lifecycle-entry-${sequenceLabel}-${(0, context_storage_1.sha256)(Buffer.from([manifest.runId, String(sequence), binding.commandIdentity, artifact.relativePath, binding.gate, status].join('\u0000'))).slice(7, 23)}`;
    const evidenceHash = (0, context_storage_1.sha256)(evidenceBytes);
    const entry = {
        schemaVersion: 1,
        entryId,
        sequence,
        commandIdentity: binding.commandIdentity,
        artifactPath: artifact.relativePath,
        artifactHash: artifact.hash,
        gate: binding.gate,
        status,
        writeOutcome,
        receiptIds,
        supersedesEntryId: null,
        evidencePath,
        evidenceHash,
        evidenceBytes: evidenceBytes.byteLength,
        evidenceKind: status === 'ok' ? 'structured-success' : 'structured-error',
        errorCode: typeof error?.code === 'string' ? error.code : null,
        recordedAt,
    };
    const rejectsChangedPathsWithoutWrite = isNoWriteChangedPathsParserRejection({
        commandIdentity: binding.commandIdentity,
        status,
        writeOutcome,
        error,
    });
    // A pre-authoring build is bound to the unchanged task definition and may safely
    // re-anchor there. Only prevent the rejected summary revision itself from becoming active.
    const preservesAcceptedArtifactRevision = rejectsChangedPathsWithoutWrite &&
        node_path_1.default.posix.basename(artifact.relativePath) === 'implementation-summary.md';
    const nextManifest = {
        ...manifest,
        phaseHash: phase.hash,
        activeArtifactPath: preservesAcceptedArtifactRevision ? manifest.activeArtifactPath : artifact.relativePath,
        activeArtifactHash: preservesAcceptedArtifactRevision ? manifest.activeArtifactHash : artifact.hash,
        activeGate: binding.gate,
        updatedAt: recordedAt,
        nextSequence: sequence + 1,
        entries: [...manifest.entries, entry],
    };
    const objectWritten = (0, context_storage_1.writeEvidenceAndManifest)(evidenceAbsolute, evidenceBytes, loaded.manifestAbsolute, nextManifest);
    return {
        manifestPath: loaded.manifestPath,
        evidencePath,
        changedPaths: [...(objectWritten ? [evidencePath] : []), loaded.manifestPath],
        writeOutcome: 'committed',
        entry,
        manifest: nextManifest,
    };
}
function isNoWriteChangedPathsParserRejection(options) {
    if (options.commandIdentity !== 'implementation-summary build' ||
        options.status !== 'error' ||
        options.writeOutcome !== 'no-writes') {
        return false;
    }
    const message = typeof options.error?.message === 'string' ? options.error.message : '';
    const details = Array.isArray(options.error?.details)
        ? options.error.details.filter((entry) => typeof entry === 'string').join('\n')
        : '';
    return /Changed Paths/i.test(`${message}\n${details}`);
}
function openLifecycleRunContext(options) {
    const repositoryRoot = (0, context_storage_1.canonicalRoot)(options.repositoryRoot);
    const runDirectoryPath = (0, sandbox_contract_1.assertSandboxRunDirectoryPath)(options.runDirectory);
    const runDirectoryTarget = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, runDirectoryPath);
    const phase = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, options.phasePath, 'Selected phase');
    const activeArtifact = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, options.activeArtifactPath, 'Active artifact');
    const openedAt = (0, context_storage_1.requireTimestamp)(options.openedAt ?? new Date().toISOString(), 'Run opening time');
    const lifecycleProfile = (0, context_storage_1.requireBoundedText)(options.lifecycleProfile, 'Lifecycle profile');
    const metadataPosture = (0, context_inspection_1.parseLifecycleMetadataPosture)(options.metadataPosture);
    const actor = (0, context_storage_1.requireBoundedText)(options.actor, 'Run actor');
    const activeGate = (0, context_storage_1.requireToken)(options.activeGate, 'Active gate');
    if (!runDirectoryTarget.exists) {
        node_fs_1.default.mkdirSync(runDirectoryTarget.runtimePath, { recursive: true, mode: 0o700 });
    }
    const runDirectory = (0, context_storage_1.requireRunDirectory)(repositoryRoot, runDirectoryPath);
    const manifestPath = (0, sandbox_contract_1.assertSandboxRunContentPath)(node_path_1.default.posix.join(runDirectory.relativePath, exports.LIFECYCLE_RUN_MANIFEST_NAME));
    const manifestAbsolute = node_path_1.default.join(repositoryRoot, manifestPath);
    if (node_fs_1.default.existsSync(manifestAbsolute)) {
        throw (0, errors_1.createUsageError)(`Lifecycle run context '${manifestPath}' already exists.`, [
            'Use resume run status or resume run resume for the existing context.',
        ]);
    }
    const runId = `lifecycle-run-${(0, context_storage_1.sha256)(Buffer.from([runDirectory.relativePath, phase.relativePath, lifecycleProfile, metadataPosture].join('\u0000'))).slice(7, 31)}`;
    const manifest = {
        schemaVersion: 1,
        runId,
        runDirectory: runDirectory.relativePath,
        phasePath: phase.relativePath,
        phaseHash: phase.hash,
        lifecycleProfile,
        metadataPosture,
        actor,
        activeArtifactPath: activeArtifact.relativePath,
        activeArtifactHash: activeArtifact.hash,
        activeGate,
        openedAt,
        updatedAt: openedAt,
        nextSequence: 1,
        entries: [],
        contractRevisions: [],
    };
    (0, context_storage_1.writeJsonAtomic)(manifestAbsolute, manifest, exports.MAX_LIFECYCLE_RUN_MANIFEST_BYTES);
    return {
        manifestPath,
        changedPaths: [manifestPath],
        writeOutcome: 'committed',
        manifest,
    };
}
function recordLifecycleRunOutcome(options) {
    const repositoryRoot = (0, context_storage_1.canonicalRoot)(options.repositoryRoot);
    const loaded = (0, context_inspection_1.readManifest)(repositoryRoot, options.runDirectory);
    const manifest = loaded.manifest;
    if (manifest.entries.length >= exports.MAX_ENTRIES) {
        throw (0, errors_1.createUsageError)(`Lifecycle run contexts may contain at most ${exports.MAX_ENTRIES} entries.`);
    }
    const result = (0, context_inspection_1.readStructuredResult)(repositoryRoot, options.resultPath);
    const commandIdentity = (0, context_storage_1.requireBoundedText)(options.commandIdentity ?? result.command, 'Command identity');
    if (options.commandIdentity != null && result.command !== null && commandIdentity !== result.command) {
        throw (0, errors_1.createUsageError)(`Selected command identity '${commandIdentity}' does not match structured result command '${result.command}'.`);
    }
    const artifact = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, options.artifactPath ?? manifest.activeArtifactPath, 'Active artifact');
    const phase = (0, context_storage_1.readBoundedRepositoryFile)(repositoryRoot, manifest.phasePath, 'Selected phase');
    const gate = (0, context_storage_1.requireToken)(options.gate, 'Active gate');
    const writeOutcome = (0, context_storage_1.requireBoundedText)(options.writeOutcome ?? result.writeOutcome ?? (result.status === 'ok' ? 'complete' : 'no-writes'), 'Write outcome');
    const recordedAt = (0, context_storage_1.requireTimestamp)(options.recordedAt ?? new Date().toISOString(), 'Outcome record time');
    const supersedesEntryId = (0, context_storage_1.optionalIdentity)(options.supersedesEntryId, 'Superseded entry identity');
    if (supersedesEntryId !== null) {
        const superseded = manifest.entries.find((entry) => entry.entryId === supersedesEntryId);
        if (superseded === undefined) {
            throw (0, errors_1.createUsageError)(`Superseded lifecycle entry '${supersedesEntryId}' does not exist.`);
        }
        if (manifest.entries.some((entry) => entry.supersedesEntryId === supersedesEntryId)) {
            throw (0, errors_1.createUsageError)(`Lifecycle entry '${supersedesEntryId}' already has a replacement.`);
        }
    }
    const sequence = manifest.nextSequence;
    const sequenceLabel = String(sequence).padStart(4, '0');
    const evidenceBytes = (0, context_storage_1.serializeEvidenceEnvelope)(result.envelope, repositoryRoot);
    const evidenceStorage = (0, evidence_store_1.createLifecycleEvidenceObjectReference)(manifest.runDirectory, evidenceBytes);
    const evidencePath = (0, sandbox_contract_1.assertSandboxRunContentPath)(evidenceStorage.objectPath);
    const evidenceAbsolute = (0, evidence_store_1.resolveLifecycleEvidenceObjectWritePathSync)(repositoryRoot, evidenceStorage.objectPath);
    const explicitReceiptIds = (0, context_storage_1.normalizeReceiptIds)(options.receiptIds ?? []);
    const receiptIds = (0, context_storage_1.normalizeReceiptIds)([...explicitReceiptIds, ...(0, context_storage_1.collectReceiptIds)(result.envelope)]);
    const entryId = `lifecycle-entry-${sequenceLabel}-${(0, context_storage_1.sha256)(Buffer.from([manifest.runId, String(sequence), commandIdentity, artifact.relativePath, gate, result.status].join('\u0000'))).slice(7, 23)}`;
    if (evidenceBytes.byteLength > exports.MAX_LIFECYCLE_RUN_RESULT_BYTES) {
        throw (0, errors_1.createUsageError)(`Lifecycle structured evidence exceeds the ${exports.MAX_LIFECYCLE_RUN_RESULT_BYTES}-byte limit.`);
    }
    const evidenceHash = evidenceStorage.objectId;
    const entry = {
        schemaVersion: 1,
        entryId,
        sequence,
        commandIdentity,
        artifactPath: artifact.relativePath,
        artifactHash: artifact.hash,
        gate,
        status: result.status,
        writeOutcome,
        receiptIds,
        supersedesEntryId,
        evidencePath,
        evidenceHash,
        evidenceBytes: evidenceBytes.byteLength,
        evidenceKind: result.status === 'ok' ? 'structured-success' : 'structured-error',
        errorCode: result.errorCode,
        recordedAt,
        sourceResultPath: result.relativePath,
        evidenceStorage,
    };
    const nextManifest = {
        ...manifest,
        phaseHash: phase.hash,
        activeArtifactPath: artifact.relativePath,
        activeArtifactHash: artifact.hash,
        activeGate: gate,
        updatedAt: recordedAt,
        nextSequence: sequence + 1,
        entries: [...manifest.entries, entry],
    };
    const objectWritten = (0, context_storage_1.writeEvidenceAndManifest)(evidenceAbsolute, evidenceBytes, loaded.manifestAbsolute, nextManifest);
    return {
        manifestPath: loaded.manifestPath,
        evidencePath,
        evidenceObjectPath: evidenceStorage.objectPath,
        changedPaths: [...(objectWritten ? [evidenceStorage.objectPath] : []), loaded.manifestPath],
        writeOutcome: 'committed',
        entry,
        manifest: nextManifest,
    };
}
