"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.STANDARD_OR_TRANSFORMED_FRONT_MATTER_FIELDS = exports.MIGRATION_NORMALIZED_LIFECYCLE_FRONT_MATTER_FIELDS = exports.CONTRACTUAL_TIMESTAMP_FIELDS = exports.LIFECYCLE_FRONT_MATTER_FIELDS = exports.SAFE_UNKNOWN_FRONT_MATTER_FIELDS = void 0;
exports.validateMappingDocument = validateMappingDocument;
exports.mappingSelections = mappingSelections;
exports.isWorkflowLevel = isWorkflowLevel;
exports.normalizeIsoTimestamp = normalizeIsoTimestamp;
exports.removeEmptyNumericPlanningRootsSync = removeEmptyNumericPlanningRootsSync;
exports.removeEmptyDirectoriesBelowSync = removeEmptyDirectoriesBelowSync;
exports.activeNumericPlanningRootPaths = activeNumericPlanningRootPaths;
exports.findDuplicateTaskIdentities = findDuplicateTaskIdentities;
exports.applicationPathsAreIsolated = applicationPathsAreIsolated;
exports.runRehearsalValidationCommands = runRehearsalValidationCommands;
exports.inspectIsolatedLinkGapEvidence = inspectIsolatedLinkGapEvidence;
exports.commandReceipt = commandReceipt;
exports.firstDestinationForRole = firstDestinationForRole;
exports.firstDestinationMatching = firstDestinationMatching;
exports.stableIdentityPreserved = stableIdentityPreserved;
exports.validateReportDocument = validateReportDocument;
exports.readRepositoryMigrationRehearsalReportSync = readRepositoryMigrationRehearsalReportSync;
exports.requireCutoverDigest = requireCutoverDigest;
exports.rejectLiveRepositoryWriteLock = rejectLiveRepositoryWriteLock;
exports.readLocalReadinessReviewEvidence = readLocalReadinessReviewEvidence;
exports.writeTrackedJsonSync = writeTrackedJsonSync;
const node_child_process_1 = require("node:child_process");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const validation_links_report_1 = require("../../validation/validation-links-report");
const operational_state_1 = require("../../filesystem/operational-state");
const contracts_1 = require("./contracts");
const filesystem_1 = require("./filesystem");
exports.SAFE_UNKNOWN_FRONT_MATTER_FIELDS = {
    mode: 'unknown',
};
exports.LIFECYCLE_FRONT_MATTER_FIELDS = {
    mode: 'selected',
    fields: new Set([
        'readiness',
        'status',
        'readiness_review_id',
        'readiness_review_revision',
        'readiness_review_protocol_version',
        'readiness_reviewed_by',
        'readiness_reviewed_at',
        'current_attempt_id',
        'attempts',
        'implementation_status',
        'review_disposition',
        'reconciliation_status',
        'reconciliation_review_id',
        'reconciliation_review_revision',
        'reconciliation_review_protocol_version',
        'reconciliation_reviewed_by',
        'reconciliation_reviewed_at',
    ]),
};
exports.CONTRACTUAL_TIMESTAMP_FIELDS = {
    mode: 'selected',
    fields: new Set(['created_at', 'updated_at']),
};
exports.MIGRATION_NORMALIZED_LIFECYCLE_FRONT_MATTER_FIELDS = {
    mode: 'selected',
    fields: new Set([...exports.LIFECYCLE_FRONT_MATTER_FIELDS.fields].filter((field) => field !== 'readiness_review_revision')),
};
exports.STANDARD_OR_TRANSFORMED_FRONT_MATTER_FIELDS = new Set([
    'artifact_schema_version',
    'artifact_type',
    'id',
    'work_id',
    'workflow_level',
    'implementation_order',
    'sequence',
    'slice',
    'phase',
    'milestone',
    'task',
    'upstream',
    ...exports.LIFECYCLE_FRONT_MATTER_FIELDS.fields,
    ...exports.CONTRACTUAL_TIMESTAMP_FIELDS.fields,
]);
function validateMappingDocument(mapping, evidence) {
    if (mapping.protocolVersion !== contracts_1.REPOSITORY_MIGRATION_REHEARSAL_PROTOCOL_VERSION ||
        mapping.kind !== 'day-shift-phase-07-repository-migration-mapping' ||
        !Array.isArray(mapping.selections) ||
        !Array.isArray(mapping.numericRoots) ||
        !Array.isArray(mapping.samePrefixRoots) ||
        !Array.isArray(mapping.absentPrefixes)) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping protocol fields are malformed.');
    }
    const { mappingDigest, ...digestInput } = mapping;
    if (mappingDigest !== (0, filesystem_1.sha256)((0, filesystem_1.stableJson)(digestInput))) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping digest does not match its content.');
    }
    const seen = new Set();
    for (const selection of mapping.selections) {
        if (typeof selection.workUnitId !== 'string' ||
            seen.has(selection.workUnitId) ||
            !isWorkflowLevel(selection.workflowLevel) ||
            !Number.isSafeInteger(selection.sourcePathCount) ||
            selection.sourcePathCount < 1 ||
            !/^sha256:[a-f0-9]{64}$/.test(selection.sourcePathsDigest) ||
            typeof selection.rationale !== 'string' ||
            selection.rationale.trim().length === 0) {
            throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping contains an unknown, duplicate, incomplete, or unsupported selection.');
        }
        seen.add(selection.workUnitId);
    }
    if (evidence === undefined)
        return;
    if (mapping.sourceManifestDigest !== evidence.sourceManifest.aggregateDigest) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping source manifest digest is stale.');
    }
    if (mapping.mappingInventoryDigest !== evidence.mappingInventory.aggregateDigest) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping inventory digest is stale.');
    }
    if ((0, filesystem_1.stableJson)(mapping.numericRoots) !== (0, filesystem_1.stableJson)(evidence.mappingInventory.numericRoots) ||
        (0, filesystem_1.stableJson)(mapping.samePrefixRoots) !== (0, filesystem_1.stableJson)(evidence.mappingInventory.samePrefixRoots) ||
        (0, filesystem_1.stableJson)(mapping.absentPrefixes) !== (0, filesystem_1.stableJson)(evidence.mappingInventory.absentPrefixes)) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping numeric-root evidence is incomplete or stale.');
    }
    const unitsById = new Map(evidence.mappingInventory.units.map((unit) => [unit.workUnitId, unit]));
    if (mapping.selections.length !== unitsById.size) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', 'Phase 07 mapping does not enumerate every current compatibility work unit.');
    }
    for (const selection of mapping.selections) {
        const unit = unitsById.get(selection.workUnitId);
        if (unit === undefined ||
            selection.sourcePathCount !== unit.sourcePaths.length ||
            selection.sourcePathsDigest !== (0, filesystem_1.sha256)((0, filesystem_1.stableJson)([...unit.sourcePaths].sort()))) {
            throw new contracts_1.RepositoryMigrationRehearsalError('invalid-mapping', `Phase 07 mapping selection '${selection.workUnitId}' has unknown or stale source evidence.`);
        }
    }
}
function mappingSelections(mapping) {
    return mapping.selections.map((selection) => ({
        workUnitId: selection.workUnitId,
        workflowLevel: selection.workflowLevel,
    }));
}
function isWorkflowLevel(value) {
    return value === 'basic' || value === 'structured' || value === 'governed';
}
function normalizeIsoTimestamp(value) {
    const date = new Date(value);
    if (!Number.isFinite(date.valueOf()) || date.toISOString() !== value) {
        throw new contracts_1.RepositoryMigrationRehearsalError('rehearsal-blocked', `Rehearsal timestamp '${value}' must be an exact ISO-8601 timestamp.`);
    }
    return value;
}
function removeEmptyNumericPlanningRootsSync(repositoryRoot) {
    const planningRoot = node_path_1.default.join(repositoryRoot, '.day-shift', 'planning');
    for (const entry of node_fs_1.default.readdirSync(planningRoot, { withFileTypes: true })) {
        if (!entry.isDirectory() || !/^\d{2}-/.test(entry.name))
            continue;
        const root = node_path_1.default.join(planningRoot, entry.name);
        (0, filesystem_1.rejectSymlinkPath)(repositoryRoot, root, `Migrated legacy root '${entry.name}'`);
        removeEmptyDirectoriesBelowSync(root);
        if (node_fs_1.default.readdirSync(root).length === 0)
            node_fs_1.default.rmdirSync(root);
    }
}
function removeEmptyDirectoriesBelowSync(directory) {
    for (const entry of node_fs_1.default.readdirSync(directory, { withFileTypes: true })) {
        if (!entry.isDirectory())
            continue;
        const child = node_path_1.default.join(directory, entry.name);
        if (node_fs_1.default.lstatSync(child).isSymbolicLink()) {
            throw new contracts_1.RepositoryMigrationRehearsalError('unsafe-target', `Migrated rehearsal cleanup rejected symlink '${(0, filesystem_1.toPosix)(child)}'.`, false);
        }
        removeEmptyDirectoriesBelowSync(child);
        if (node_fs_1.default.readdirSync(child).length === 0)
            node_fs_1.default.rmdirSync(child);
    }
}
function activeNumericPlanningRootPaths(repositoryRoot) {
    const planningRoot = node_path_1.default.join(repositoryRoot, '.day-shift', 'planning');
    return node_fs_1.default
        .readdirSync(planningRoot, { withFileTypes: true })
        .filter((entry) => entry.isDirectory() && /^\d{2}-/.test(entry.name))
        .map((entry) => `.day-shift/planning/${entry.name}`)
        .sort();
}
function findDuplicateTaskIdentities(manifest) {
    const counts = new Map();
    for (const entry of manifest.entries) {
        if (entry.identity?.artifactType !== 'task_definition')
            continue;
        if (entry.identity.status === 'completed' || entry.identity.status === 'cancelled') {
            continue;
        }
        const identity = entry.identity.task ?? entry.identity.id;
        if (identity === null)
            continue;
        const key = `${entry.identity.workId ?? ''}\0${identity}`;
        counts.set(key, (counts.get(key) ?? 0) + 1);
    }
    return [...counts.entries()]
        .filter(([, count]) => count > 1)
        .map(([identity]) => identity.replace('\0', ':'))
        .sort();
}
function applicationPathsAreIsolated(application) {
    const paths = [
        ...application.sourcePaths,
        ...application.destinationPaths,
        ...application.changedPaths,
        ...application.retiredPaths,
        application.migrationRecordPath,
        ...(application.transactionPath === null ? [] : [application.transactionPath]),
        ...(application.recoveryPath === null ? [] : [application.recoveryPath]),
    ];
    return paths.every((entry) => !node_path_1.default.isAbsolute(entry) &&
        node_path_1.default.posix.normalize(entry) === entry &&
        !entry.startsWith('../') &&
        entry.startsWith('.day-shift/'));
}
function runRehearsalValidationCommands(canonicalRoot, isolatedRoot, plan) {
    const cliPath = node_path_1.default.join(canonicalRoot, 'dist', 'cli', 'index.js');
    (0, filesystem_1.requireFile)(cliPath, 'Built Day Shift CLI');
    const taskPath = firstDestinationForRole(plan, 'task-definition');
    const milestonePath = firstDestinationMatching(plan, /\/milestone-overview\.md$/);
    const phasePath = firstDestinationMatching(plan, /\/phase-overview\.md$/);
    const isolatedCommands = [
        {
            id: 'isolated-ownership-compatibility-gate',
            args: ['agent'],
            acceptedGap: 'The intentionally minimal isolated copy omits canonical prompt/template asset manifests; command ownership is isolated and subsequent read-only checks use the documented compatibility-gate bypass.',
        },
        { id: 'isolated-config-inventory', args: ['config', 'inventory'] },
        {
            id: 'isolated-planning-inventory',
            args: ['planning', 'inventory'],
            acceptedGap: 'The current inventory reader does not yet admit canonical work_overview artifacts; structural verification independently proves every projected unit is canonical and non-actionable.',
        },
        {
            id: 'isolated-links-lifecycle',
            args: ['validation', 'links-check', '--scope', 'planning', '--format', 'json'],
            acceptedGap: 'Legacy link/lifecycle findings are retained as command evidence and do not mutate migrated history.',
        },
        {
            id: 'isolated-validation-smoke',
            args: ['validation', 'smoke', '--scope', 'planning'],
            acceptedGap: 'Legacy smoke findings are retained as command evidence and do not mutate migrated history.',
        },
        { id: 'isolated-command-protocol', args: ['commands', 'validate'] },
        ...(taskPath === null
            ? []
            : [
                {
                    id: 'isolated-task-review',
                    args: ['task', 'review', '--task-definition', taskPath],
                    acceptedGap: 'Representative read-only review may retain legacy lifecycle findings.',
                },
            ]),
        ...(milestonePath === null
            ? []
            : [
                {
                    id: 'isolated-milestone-review',
                    args: ['milestone', 'review', '--milestone-overview', milestonePath],
                    acceptedGap: 'Representative read-only review may retain legacy reconciliation findings.',
                },
            ]),
        ...(phasePath === null
            ? []
            : [
                {
                    id: 'isolated-phase-review',
                    args: ['phase', 'review', '--phase-overview', phasePath],
                    acceptedGap: 'Representative read-only review may retain legacy reconciliation findings.',
                },
            ]),
    ];
    const receipts = isolatedCommands.map((command, index) => commandReceipt(cliPath, command.id, command.args, isolatedRoot, canonicalRoot, index === 0 ? false : true, command.acceptedGap ?? null, false));
    receipts.push(commandReceipt(cliPath, 'canonical-planning-inventory-excludes-sandbox', ['planning', 'inventory'], canonicalRoot, canonicalRoot, true, null, true), commandReceipt(cliPath, 'canonical-command-protocol', ['commands', 'validate'], canonicalRoot, canonicalRoot, true, null, true));
    return receipts;
}
function inspectIsolatedLinkGapEvidence(repositoryRoot) {
    const report = (0, validation_links_report_1.runValidationLinksCheckReport)({ repositoryRoot });
    const findingCountsByCode = {};
    const representativeByCode = new Map();
    for (const finding of report.findings) {
        findingCountsByCode[finding.code] = (findingCountsByCode[finding.code] ?? 0) + 1;
        const representative = finding.affectedPaths?.[0]?.path ?? finding.artifactPath;
        if (representative !== undefined && !representativeByCode.has(finding.code)) {
            representativeByCode.set(finding.code, representative);
        }
    }
    return {
        findingCount: report.findings.length,
        blockingFindingCount: report.findings.filter((finding) => finding.severity === 'error').length,
        advisoryFindingCount: report.findings.filter((finding) => finding.severity !== 'error').length,
        findingCountsByCode: Object.fromEntries(Object.entries(findingCountsByCode).sort(([left], [right]) => left.localeCompare(right))),
        representativePaths: [...representativeByCode.entries()]
            .sort(([left], [right]) => left.localeCompare(right))
            .map(([, representative]) => representative),
    };
}
function commandReceipt(cliPath, id, args, cwd, canonicalRoot, skipCompatibilityGate, acceptedGap, canonicalCommand) {
    const result = (0, node_child_process_1.spawnSync)(process.execPath, [cliPath, ...args], {
        cwd,
        encoding: 'utf8',
        env: {
            ...process.env,
            ...(skipCompatibilityGate ? { DAY_SHIFT_SKIP_WORKSPACE_COMPATIBILITY_GATE: '1' } : {}),
        },
        maxBuffer: 64 * 1024 * 1024,
    });
    const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
    const sandboxPathLeak = output.includes(contracts_1.REPOSITORY_MIGRATION_REHEARSAL_ROOT);
    const hostAbsolutePathLeak = output.includes(canonicalRoot);
    const pathSafe = canonicalCommand ? !sandboxPathLeak && !hostAbsolutePathLeak : !hostAbsolutePathLeak;
    const exitAccepted = result.status === 0 || acceptedGap !== null;
    return {
        id,
        command: ['day-shift', ...args],
        exitCode: result.status,
        outputDigest: (0, filesystem_1.sha256)(output),
        accepted: pathSafe && exitAccepted,
        acceptedGap: result.status === 0 ? null : acceptedGap,
        sandboxPathLeak,
        hostAbsolutePathLeak,
    };
}
function firstDestinationForRole(plan, role) {
    for (const unit of plan.workUnits) {
        const mapping = unit.artifactMappings.find((entry) => entry.role === role);
        if (mapping !== undefined)
            return mapping.destinationPath;
    }
    return null;
}
function firstDestinationMatching(plan, pattern) {
    return (plan.workUnits.flatMap((unit) => unit.destinationPaths).find((destination) => pattern.test(destination)) ?? null);
}
function stableIdentityPreserved(source, destination, canonicalSpecificationRefs) {
    if (source === null)
        return destination === null;
    if (destination === null)
        return false;
    if ((0, filesystem_1.stableJson)(source.specificationIds) !== (0, filesystem_1.stableJson)(destination.specificationIds) ||
        (0, filesystem_1.stableJson)(source.specificationRefs.map((ref) => canonicalSpecificationRefs.get(ref) ?? ref)) !==
            (0, filesystem_1.stableJson)(destination.specificationRefs) ||
        source.task !== destination.task) {
        return false;
    }
    if (source.workId !== null && source.workId !== destination.workId)
        return false;
    if (source.task !== null && source.id !== destination.id)
        return false;
    return true;
}
function validateReportDocument(report) {
    if (report.protocolVersion !== contracts_1.REPOSITORY_MIGRATION_REHEARSAL_PROTOCOL_VERSION ||
        report.kind !== 'day-shift-phase-07-repository-migration-rehearsal' ||
        (report.verdict !== 'accepted' && report.verdict !== 'blocked') ||
        typeof report.reportDigest !== 'string') {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-report', 'Phase 07 rehearsal report protocol fields are malformed.');
    }
    const { reportDigest, ...digestInput } = report;
    if (reportDigest !== (0, filesystem_1.sha256)((0, filesystem_1.stableJson)(digestInput))) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-report', 'Phase 07 rehearsal report digest does not match its content.');
    }
}
function readRepositoryMigrationRehearsalReportSync(repositoryRoot) {
    const reportPath = node_path_1.default.join(repositoryRoot, ...contracts_1.REPOSITORY_MIGRATION_REHEARSAL_REPORT_PATH.split('/'));
    (0, filesystem_1.requireFile)(reportPath, 'Phase 07 migration rehearsal report');
    (0, filesystem_1.rejectSymlinkPath)(repositoryRoot, reportPath, 'Phase 07 migration rehearsal report');
    let report;
    try {
        report = JSON.parse(node_fs_1.default.readFileSync(reportPath, 'utf8'));
    }
    catch {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-report', 'Phase 07 rehearsal report is not valid JSON.');
    }
    validateReportDocument(report);
    return report;
}
function requireCutoverDigest(value) {
    if (!/^sha256:[a-f0-9]{64}$/.test(value)) {
        throw new contracts_1.RepositoryMigrationRehearsalError('invalid-cutover-request', 'Canonical cutover requires an exact lowercase sha256 rehearsal report digest.');
    }
    return value;
}
function rejectLiveRepositoryWriteLock(repositoryRoot) {
    const lock = (0, operational_state_1.inspectOperationalStateSync)(repositoryRoot).lock;
    if (lock?.classification === 'live') {
        throw new contracts_1.RepositoryMigrationRehearsalError('cutover-blocked', `Canonical cutover rejected the active repository write lock '${lock.path}'${lock.transactionId === null ? '' : ` for transaction '${lock.transactionId}'`}. No write was attempted.`);
    }
}
function readLocalReadinessReviewEvidence(repositoryRoot) {
    const localRoot = node_path_1.default.join(repositoryRoot, '.day-shift', 'local', 'task-lifecycle-reviews');
    const evidence = new Map();
    if (!node_fs_1.default.existsSync(localRoot) || !node_fs_1.default.statSync(localRoot).isDirectory()) {
        return evidence;
    }
    for (const absolutePath of (0, filesystem_1.walkFiles)(localRoot)) {
        if (!absolutePath.endsWith('.json'))
            continue;
        const contents = node_fs_1.default.readFileSync(absolutePath);
        try {
            const parsed = JSON.parse(contents.toString('utf8'));
            if (typeof parsed.readinessReviewId !== 'string')
                continue;
            evidence.set(parsed.readinessReviewId, {
                path: (0, filesystem_1.toPosix)(node_path_1.default.relative(repositoryRoot, absolutePath)),
                digest: (0, filesystem_1.sha256)(contents),
            });
        }
        catch {
            continue;
        }
    }
    return evidence;
}
function writeTrackedJsonSync(repositoryRoot, relativePath, value) {
    if (relativePath !== contracts_1.REPOSITORY_MIGRATION_MAPPING_PATH &&
        relativePath !== contracts_1.REPOSITORY_MIGRATION_REHEARSAL_REPORT_PATH) {
        throw new contracts_1.RepositoryMigrationRehearsalError('unsafe-target', 'Rehearsal tracked writes are restricted to the declared protocol-v1 mapping and report paths.');
    }
    const absolutePath = node_path_1.default.join(repositoryRoot, ...relativePath.split('/'));
    (0, filesystem_1.rejectSymlinkAncestors)(repositoryRoot, absolutePath);
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(absolutePath), { recursive: true });
    node_fs_1.default.writeFileSync(absolutePath, `${JSON.stringify(value, null, 2)}\n`, {
        encoding: 'utf8',
        mode: 0o644,
    });
}
