import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { TextDecoder } from 'node:util';
import { z } from 'zod';
import { compareCodeUnits } from '../utils.js';
import { runGit, runGitRaw } from '../git/client.js';
import { readGitDagHistory } from '../git/dag.js';
import { readGitObjectHeaders, readGitObjects } from '../git/objects.js';
import { createTokenizer } from '../tokens.js';
import { GIT_SPAN_DIGEST_ALGORITHM, GitCoordinateCoverageV1Schema, GitCoordinateSourceSpanV1Schema, GitNativeCoordinateV1Schema, computeGitSpanDigest, selectGitCoordinateSpan, } from './coordinates.js';
import { moduleForRelativePath } from './query.js';
import { computeEntityDiff, entityRefOrdinalFor } from './entity/diff.js';
import { isEntityEvidenceGapDiagnostic } from './entity/evidence.js';
import { listTreeBlobs, readRawDiff } from './entity/git.js';
import { createMemoryBlobCache } from './entity/indexer.js';
import { entityStateKey, ENTITY_PARSER_CONTRACT, ENTITY_PARSER_CONTRACT_ID } from './entity/snapshot.js';
import { openEntityStore } from './entity/store.js';
import { createTestRecognizer } from './tests.js';
import { DEFAULT_ENTITY_BOUNDS, DEFAULT_LINEAGE_THRESHOLDS, LineageMatchReasonSchema } from './entity/types.js';
const ATOMS_SCHEMA_VERSION_V1 = 1;
const ATOMS_SCHEMA_VERSION = 2;
const LINEAGE_ALGORITHM = 'dag-branch-union-v1';
const EXACT_TOKENIZERS = ['o200k_base', 'cl100k_base'];
const FULL_OID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
const FATAL_UTF8 = new TextDecoder('utf-8', { fatal: true });
export const AtomCoverageSchema = GitCoordinateCoverageV1Schema;
export const AtomSourceSpanV1Schema = GitCoordinateSourceSpanV1Schema;
export const AtomCoordinateV1Schema = GitNativeCoordinateV1Schema;
export const AtomChangeFlagsV1Schema = z
    .object({
    path_changed: z.boolean().optional(),
    name_changed: z.boolean().optional(),
    container_changed: z.boolean().optional(),
    kind_changed: z.boolean().optional(),
    body_changed: z.boolean().optional(),
    logic_changed: z.boolean().optional(),
    cosmetic_only: z.boolean().optional(),
    order_changed: z.boolean().optional(),
    signature_changed: z.boolean().optional(),
    export_changed: z.boolean().optional(),
    span_inexact: z.boolean().optional(),
    git_rename_applied: z.boolean().optional(),
    ambiguity_refused: z.boolean().optional(),
    evidence_unavailable: z.boolean().optional(),
    bound_hit: z.boolean().optional(),
    birth_at_window_boundary: z.boolean().optional(),
})
    .strict();
export const AtomMatchEvidenceV1Schema = z
    .object({
    pool: z.enum(['same-path', 'git-rename', 'cross-file', 'none']),
    hashes_comparable: z.boolean(),
    body_hash_equal: z.boolean().optional(),
    normalized_hash_equal: z.boolean().optional(),
    structural_hash_equal: z.boolean().optional(),
    signature_hash_equal: z.boolean().optional(),
    similarity: z.number().min(0).max(1).optional(),
    runner_up_similarity: z.number().min(0).max(1).optional(),
    tiebreak_similarity: z.number().min(0).max(1).optional(),
    candidate_count: z.number().int().nonnegative().optional(),
    git_rename_similarity: z.number().int().optional(),
})
    .strict();
export const AtomEntityChangeV1Schema = z
    .object({
    before: AtomCoordinateV1Schema.nullable(),
    after: AtomCoordinateV1Schema.nullable(),
    change_class: z.enum(['added', 'removed', 'moved', 'renamed', 'reordered', 'logic', 'cosmetic', 'unknown']),
    semantic_class: z.enum(['logic', 'cosmetic', 'neutral', 'unknown']),
    lineage_id: z.string().regex(/^dag1:[0-9a-f]{32}$/),
    match_reason: LineageMatchReasonSchema,
    /** Ordered evidence-ladder rung score, not a probability. */
    match_confidence: z.number().min(0).max(1),
    match_evidence: AtomMatchEvidenceV1Schema.optional(),
    flags: AtomChangeFlagsV1Schema,
})
    .strict();
export const AtomDiagnosticV1Schema = z
    .object({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION_V1),
    type: z.literal('diagnostic'),
    severity: z.enum(['warning', 'error']),
    code: z.string().min(1),
    message: z.string(),
    parent_ref: z.string().regex(FULL_OID).nullable().optional(),
    child_ref: z.string().regex(FULL_OID).optional(),
    path: z.string().optional(),
})
    .strict();
const AtomDiagnosticPayloadV1Schema = AtomDiagnosticV1Schema.omit({ schema_version: true, type: true });
export const AtomMeasuresV1Schema = z
    .object({
    basis: z.literal('git-patch-added-deleted-payload'),
    tokens: z.number().int().nonnegative(),
    bytes: z.number().int().nonnegative(),
    lines: z.number().int().nonnegative(),
    tokenizer: z.enum(EXACT_TOKENIZERS),
    coverage: AtomCoverageSchema,
})
    .strict();
export const AtomModulePairV1Schema = z
    .object({
    key: z.string().regex(/^module-pair:/),
    module_a: z.string().min(1),
    module_b: z.string().min(1),
})
    .strict();
export const AtomModulePairsTouchedV1Schema = z
    .object({
    count: z.number().int().nonnegative(),
    modules: z.array(z.string().min(1)),
    pairs: z.array(AtomModulePairV1Schema),
    basis: z.literal('changed-directory-module-pairs'),
    coverage: AtomCoverageSchema,
})
    .strict();
export const AtomRevertEdgeV1Schema = z
    .object({
    child_ref: z.string().regex(FULL_OID),
    parent_ref: z.string().regex(FULL_OID).nullable(),
    parent_index: z.number().int().nonnegative().nullable(),
})
    .strict();
const AtomEpisodeCoverageV1Schema = z
    .object({
    entities: AtomCoverageSchema,
    lineages: AtomCoverageSchema,
    measures: AtomCoverageSchema,
    patches: AtomCoverageSchema,
    module_pairs: AtomCoverageSchema,
})
    .strict();
export const AtomEpisodeV1Schema = z
    .object({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION_V1),
    type: z.literal('episode'),
    cursor: z.string().min(1),
    parent_ref: z.string().regex(FULL_OID).nullable(),
    child_ref: z.string().regex(FULL_OID),
    parent_index: z.number().int().nonnegative().nullable(),
    observed_at: z.string().min(1),
    message_ref: z.string().regex(FULL_OID),
    author: z.object({ name: z.string(), email: z.string(), authored_at: z.string().min(1) }).strict(),
    entities: z.array(AtomEntityChangeV1Schema),
    measures: AtomMeasuresV1Schema,
    patch_id: z.string().regex(FULL_OID).nullable(),
    revert_of: AtomRevertEdgeV1Schema.nullable(),
    module_pairs_touched: AtomModulePairsTouchedV1Schema,
    coverage: AtomEpisodeCoverageV1Schema,
    diagnostics: z.array(AtomDiagnosticPayloadV1Schema),
})
    .strict();
export const AtomLineageHeadV1Schema = z
    .object({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION_V1),
    type: z.literal('lineage-head'),
    lineage_id: z.string().regex(/^dag1:[0-9a-f]{32}$/),
    coordinate: AtomCoordinateV1Schema.nullable(),
    entity_lineage_id: z
        .string()
        .regex(/^ent[0-9]+:[0-9a-f]{32}$/)
        .nullable(),
    resolution: z.enum(['resolved', 'not_found', 'ambiguous', 'unavailable']),
    diagnostics: z.array(AtomDiagnosticPayloadV1Schema),
})
    .strict();
export const AtomEntityBoundsV1Schema = z
    .object({
    max_commits: z.number().int().positive(),
    max_files_per_commit: z.number().int().positive(),
    max_blob_bytes: z.number().int().positive(),
    max_blob_lines: z.number().int().positive(),
    max_total_blob_bytes: z.number().int().positive(),
    max_batch_bytes: z.number().int().positive(),
    max_entities_per_file: z.number().int().positive(),
    max_entities_total: z.number().int().positive(),
    max_live_entities: z.number().int().positive(),
    max_lineage_events: z.number().int().positive(),
    max_cross_file_candidates: z.number().int().positive(),
    max_similarity_candidates: z.number().int().positive(),
})
    .strict();
export const AtomHeaderV1Schema = z
    .object({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION_V1),
    type: z.literal('header'),
    requested_ref: z.string(),
    resolved_ref: z.string().regex(FULL_OID),
    since_ref: z.string().regex(FULL_OID).nullable(),
    resumed_from: z.string().nullable(),
    object_format: z.enum(['sha1', 'sha256']),
    shallow: z.boolean(),
    lineage_run_id: z.string().regex(/^atomrun1:[0-9a-f]{32}$/),
    tokenizer: z.enum(EXACT_TOKENIZERS),
    formats: z.boolean(),
    bounds: AtomEntityBoundsV1Schema,
    lineage_thresholds: z
        .object({
        similarity_min: z.number().min(0).max(1),
        similarity_margin: z.number().min(0).max(1),
        similarity_min_tokens: z.number().int().positive(),
        similarity_length_ratio_min: z.number().min(0).max(1),
        shingle_width: z.number().int().positive(),
    })
        .strict(),
    provenance: z
        .object({
        instrument: z.literal('occ.atoms'),
        instrument_version: z.literal(1),
        occ_version: z.string(),
        node_version: z.string(),
    })
        .strict(),
    policy: z
        .object({
        topology: z.literal('all-parent-edges'),
        order: z.literal('parents-before-children'),
        replacements: z.literal('disabled'),
        grafts: z.literal('disabled'),
        lazy_fetch: z.literal('disabled'),
        source_bodies: z.literal('omitted'),
        span_digest: z.literal(GIT_SPAN_DIGEST_ALGORITHM),
        lineage: z.literal(LINEAGE_ALGORITHM),
        lineage_matcher: z.literal('entity-evidence-ladder-v1'),
        lineage_confidence: z.literal('ordered-rung-score-not-probability'),
        entity_parser: z.literal('occ.code.parsers@1'),
        module_pair_basis: z.literal('changed-directory-module-pairs'),
        native_identity: z.literal('git-oid-path-span-digest'),
        entity_lineage_mapping: z.literal('pinned-first-parent-entity-index'),
        checkout_node_ids: z.literal('omitted'),
        resume_computation: z.literal('warm-replay'),
    })
        .strict(),
})
    .strict();
export const AtomFooterV1Schema = z
    .object({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION_V1),
    type: z.literal('footer'),
    complete: z.boolean(),
    coverage: z
        .object({
        history: AtomCoverageSchema,
        entities: AtomCoverageSchema,
        lineages: AtomCoverageSchema,
        head_mapping: AtomCoverageSchema,
        measures: AtomCoverageSchema,
        patches: AtomCoverageSchema,
        module_pairs: AtomCoverageSchema,
    })
        .strict(),
    counts: z
        .object({
        commits_observed: z.number().int().nonnegative(),
        transitions_observed: z.number().int().nonnegative(),
        episodes_emitted: z.number().int().nonnegative(),
        entities_emitted: z.number().int().nonnegative(),
        lineages_observed: z.number().int().nonnegative(),
        lineage_heads_emitted: z.number().int().nonnegative(),
        diagnostics: z.number().int().nonnegative(),
    })
        .strict(),
})
    .strict();
export const OccAtomsEventV1Schema = z.discriminatedUnion('type', [
    AtomHeaderV1Schema,
    AtomDiagnosticV1Schema,
    AtomEpisodeV1Schema,
    AtomLineageHeadV1Schema,
    AtomFooterV1Schema,
]);
const CursorPayloadV1Schema = z
    .object({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION_V1),
    resolved_ref: z.string().regex(FULL_OID),
    since_ref: z.string().regex(FULL_OID).nullable(),
    parent_ref: z.string().regex(FULL_OID).nullable(),
    child_ref: z.string().regex(FULL_OID),
    parent_index: z.number().int().nonnegative().nullable(),
    lineage_run_id: z.string().regex(/^atomrun1:[0-9a-f]{32}$/),
    occ_version: z.string().min(1),
    lineage_algorithm: z.literal(LINEAGE_ALGORITHM),
    entity_parser: z.literal('occ.code.parsers@1'),
    tokenizer: z.enum(EXACT_TOKENIZERS),
    bounds: AtomEntityBoundsV1Schema,
    formats: z.boolean(),
    options_fingerprint: z.string().regex(/^[0-9a-f]{64}$/),
})
    .strict();
export const AtomChangeFlagsV2Schema = AtomChangeFlagsV1Schema.extend({
    signature_evidence_unavailable: z.boolean().optional(),
    semantic_basis: z
        .enum([
        'nontrivia-syntax-change',
        'whitespace-significance-unverified',
        'comment-only-change',
        'syntax-evidence-unavailable',
        'grammar-verification-refused',
    ])
        .optional(),
}).strict();
export const AtomMatchEvidenceV2Schema = AtomMatchEvidenceV1Schema.extend({
    exact_body_hash_equal: z.boolean().optional(),
    signature_evidence_equal: z.boolean().optional(),
    reverse_runner_up_similarity: z.number().min(0).max(1).optional(),
}).strict();
export const AtomEntityChangeV2Schema = AtomEntityChangeV1Schema.omit({
    match_evidence: true,
    flags: true,
})
    .extend({
    match_evidence: AtomMatchEvidenceV2Schema.optional(),
    flags: AtomChangeFlagsV2Schema,
})
    .strict();
export const AtomDiagnosticV2Schema = AtomDiagnosticV1Schema.omit({ schema_version: true })
    .extend({ schema_version: z.literal(ATOMS_SCHEMA_VERSION) })
    .strict();
const AtomDiagnosticPayloadV2Schema = AtomDiagnosticV2Schema.omit({ schema_version: true, type: true });
export const AtomEpisodeV2Schema = AtomEpisodeV1Schema.omit({
    schema_version: true,
    entities: true,
    diagnostics: true,
})
    .extend({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION),
    entities: z.array(AtomEntityChangeV2Schema),
    diagnostics: z.array(AtomDiagnosticPayloadV2Schema),
})
    .strict();
export const AtomLineageHeadV2Schema = AtomLineageHeadV1Schema.omit({
    schema_version: true,
    diagnostics: true,
})
    .extend({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION),
    diagnostics: z.array(AtomDiagnosticPayloadV2Schema),
})
    .strict();
export const AtomHeaderV2Schema = AtomHeaderV1Schema.omit({
    schema_version: true,
    provenance: true,
    policy: true,
})
    .extend({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION),
    lineage_run_id: z.string().regex(/^atomrun2:[0-9a-f]{32}$/),
    provenance: AtomHeaderV1Schema.shape.provenance
        .omit({ instrument_version: true })
        .extend({ instrument_version: z.literal(ATOMS_SCHEMA_VERSION) })
        .strict(),
    policy: AtomHeaderV1Schema.shape.policy
        .omit({ lineage_matcher: true, entity_parser: true })
        .extend({
        lineage_matcher: z.literal('entity-evidence-ladder-v2'),
        entity_parser: z.union([
            z.literal('occ.code.parsers@2'),
            z.literal('occ.code.parsers@3'),
            z.literal(ENTITY_PARSER_CONTRACT_ID),
        ]),
    })
        .strict(),
})
    .strict();
export const AtomFooterV2Schema = AtomFooterV1Schema.omit({ schema_version: true })
    .extend({ schema_version: z.literal(ATOMS_SCHEMA_VERSION) })
    .strict();
export const OccAtomsEventV2Schema = z.discriminatedUnion('type', [
    AtomHeaderV2Schema,
    AtomDiagnosticV2Schema,
    AtomEpisodeV2Schema,
    AtomLineageHeadV2Schema,
    AtomFooterV2Schema,
]);
const CursorPayloadV2Schema = CursorPayloadV1Schema.omit({
    schema_version: true,
    lineage_run_id: true,
    entity_parser: true,
})
    .extend({
    schema_version: z.literal(ATOMS_SCHEMA_VERSION),
    lineage_run_id: z.string().regex(/^atomrun2:[0-9a-f]{32}$/),
    entity_parser: z.union([
        z.literal('occ.code.parsers@2'),
        z.literal('occ.code.parsers@3'),
        z.literal(ENTITY_PARSER_CONTRACT_ID),
    ]),
})
    .strict();
function digest(material) {
    return createHash('sha256').update(material).digest('hex');
}
function canonicalJson(value) {
    if (Array.isArray(value))
        return `[${value.map(canonicalJson).join(',')}]`;
    if (value && typeof value === 'object') {
        const entries = Object.entries(value).sort(([a], [b]) => compareCodeUnits(a, b));
        return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
    }
    return JSON.stringify(value);
}
function boundsToWire(bounds) {
    return {
        max_commits: bounds.maxCommits,
        max_files_per_commit: bounds.maxFilesPerCommit,
        max_blob_bytes: bounds.maxBlobBytes,
        max_blob_lines: bounds.maxBlobLines,
        max_total_blob_bytes: bounds.maxTotalBlobBytes,
        max_batch_bytes: bounds.maxBatchBytes,
        max_entities_per_file: bounds.maxEntitiesPerFile,
        max_entities_total: bounds.maxEntitiesTotal,
        max_live_entities: bounds.maxLiveEntities,
        max_lineage_events: bounds.maxLineageEvents,
        max_cross_file_candidates: bounds.maxCrossFileCandidates,
        max_similarity_candidates: bounds.maxSimilarityCandidates,
    };
}
function boundsFromWire(bounds) {
    return {
        maxCommits: bounds.max_commits,
        maxFilesPerCommit: bounds.max_files_per_commit,
        maxBlobBytes: bounds.max_blob_bytes,
        maxBlobLines: bounds.max_blob_lines,
        maxTotalBlobBytes: bounds.max_total_blob_bytes,
        maxBatchBytes: bounds.max_batch_bytes,
        maxEntitiesPerFile: bounds.max_entities_per_file,
        maxEntitiesTotal: bounds.max_entities_total,
        maxLiveEntities: bounds.max_live_entities,
        maxLineageEvents: bounds.max_lineage_events,
        maxCrossFileCandidates: bounds.max_cross_file_candidates,
        maxSimilarityCandidates: bounds.max_similarity_candidates,
    };
}
function cursorFingerprintV1(input) {
    return digest(canonicalJson({ schema_version: ATOMS_SCHEMA_VERSION_V1, ...input }));
}
function cursorFingerprint(input) {
    return digest(canonicalJson({ schema_version: ATOMS_SCHEMA_VERSION, ...input }));
}
function encodeCursor(payload) {
    return Buffer.from(canonicalJson(payload), 'utf8').toString('base64url');
}
function decodeCursorJson(cursor) {
    let parsed;
    try {
        parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
    }
    catch (error) {
        throw new Error(`Invalid atoms resume cursor: ${error instanceof Error ? error.message : String(error)}`);
    }
    return parsed;
}
/** Decode a persisted V1 cursor for receipt verification only. */
export function decodeAtomsCursorV1(cursor) {
    const checked = CursorPayloadV1Schema.safeParse(decodeCursorJson(cursor));
    if (!checked.success)
        throw new Error(`Invalid atoms resume cursor: ${checked.error.message}`);
    const { resolved_ref, since_ref, tokenizer, bounds, formats, lineage_run_id, occ_version, lineage_algorithm, entity_parser, options_fingerprint, } = checked.data;
    const expected = cursorFingerprintV1({
        resolved_ref,
        since_ref,
        tokenizer,
        bounds,
        formats,
        lineage_run_id,
        occ_version,
        lineage_algorithm,
        entity_parser,
    });
    if (expected !== options_fingerprint)
        throw new Error('Invalid atoms resume cursor: options fingerprint mismatch.');
    return checked.data;
}
/** Decode a V2 cursor. V1 cursors are readable but intentionally not resumable. */
export function decodeAtomsCursor(cursor) {
    const parsed = decodeCursorJson(cursor);
    if (typeof parsed === 'object' &&
        parsed !== null &&
        'schema_version' in parsed &&
        parsed.schema_version === ATOMS_SCHEMA_VERSION_V1) {
        throw new Error('Atoms resume cursor schema 1 cannot resume the V2 evidence contract. Start a new run and use its V2 cursor.');
    }
    const checked = CursorPayloadV2Schema.safeParse(parsed);
    if (!checked.success)
        throw new Error(`Invalid atoms resume cursor: ${checked.error.message}`);
    const { resolved_ref, since_ref, tokenizer, bounds, formats, lineage_run_id, occ_version, lineage_algorithm, entity_parser, options_fingerprint, } = checked.data;
    const expected = cursorFingerprint({
        resolved_ref,
        since_ref,
        tokenizer,
        bounds,
        formats,
        lineage_run_id,
        occ_version,
        lineage_algorithm,
        entity_parser,
    });
    if (expected !== options_fingerprint)
        throw new Error('Invalid atoms resume cursor: options fingerprint mismatch.');
    return checked.data;
}
function resolveOptions(options) {
    if (options.resume) {
        if (options.ref || options.since || options.tokenizer || options.bounds || options.formats !== undefined) {
            throw new Error('--resume cannot be combined with ref, since, tokenizer, bounds, or format options.');
        }
        const cursor = decodeAtomsCursor(options.resume);
        if (cursor.entity_parser !== ENTITY_PARSER_CONTRACT_ID) {
            throw new Error(`The atoms cursor pins ${cursor.entity_parser}; this build uses ${ENTITY_PARSER_CONTRACT_ID}. Start a new run.`);
        }
        return {
            repoRoot: path.resolve(options.repoPath),
            ref: cursor.resolved_ref,
            ...(cursor.since_ref ? { since: cursor.since_ref } : {}),
            resumeCursor: cursor,
            tokenizer: cursor.tokenizer,
            bounds: boundsFromWire(cursor.bounds),
            thresholds: DEFAULT_LINEAGE_THRESHOLDS,
            formats: cursor.formats,
            ...(options.signal ? { signal: options.signal } : {}),
        };
    }
    if (!options.ref)
        throw new Error('occ atoms requires ref or resume.');
    const tokenizer = options.tokenizer ?? 'o200k_base';
    if (!EXACT_TOKENIZERS.includes(tokenizer)) {
        throw new Error(`Atoms requires an exact tokenizer, received ${tokenizer}.`);
    }
    return {
        repoRoot: path.resolve(options.repoPath),
        ref: options.ref,
        ...(options.since ? { since: options.since } : {}),
        tokenizer,
        bounds: options.bounds ?? DEFAULT_ENTITY_BOUNDS,
        thresholds: DEFAULT_LINEAGE_THRESHOLDS,
        formats: options.formats ?? true,
        ...(options.signal ? { signal: options.signal } : {}),
    };
}
let cachedOccVersion;
async function readOccVersion() {
    if (cachedOccVersion)
        return cachedOccVersion;
    const directory = path.dirname(fileURLToPath(import.meta.url));
    for (const relative of ['../..', '../../..']) {
        try {
            const parsed = JSON.parse(await readFile(path.resolve(directory, relative, 'package.json'), 'utf8'));
            if (parsed.version)
                return (cachedOccVersion = parsed.version);
        }
        catch {
            // Try the source and compiled layouts.
        }
    }
    return (cachedOccVersion = '0.0.0');
}
function diagnostic(code, message, options = {}) {
    return {
        schema_version: ATOMS_SCHEMA_VERSION,
        type: 'diagnostic',
        severity: options.severity ?? 'warning',
        code,
        message,
        ...(options.parentRef !== undefined ? { parent_ref: options.parentRef } : {}),
        ...(options.childRef ? { child_ref: options.childRef } : {}),
        ...(options.path ? { path: options.path } : {}),
    };
}
function payloadDiagnostic(value) {
    const { schema_version: _schemaVersion, type: _type, ...payload } = value;
    return payload;
}
function fullDiagnostic(payload) {
    return { schema_version: ATOMS_SCHEMA_VERSION, type: 'diagnostic', ...payload };
}
function flagsToWire(flags) {
    return {
        ...(flags.pathChanged ? { path_changed: true } : {}),
        ...(flags.nameChanged ? { name_changed: true } : {}),
        ...(flags.containerChanged ? { container_changed: true } : {}),
        ...(flags.kindChanged ? { kind_changed: true } : {}),
        ...(flags.bodyChanged ? { body_changed: true } : {}),
        ...(flags.logicChanged ? { logic_changed: true } : {}),
        ...(flags.cosmeticOnly ? { cosmetic_only: true } : {}),
        ...(flags.orderChanged ? { order_changed: true } : {}),
        ...(flags.signatureChanged ? { signature_changed: true } : {}),
        ...(flags.signatureEvidenceUnavailable ? { signature_evidence_unavailable: true } : {}),
        ...(flags.exportChanged ? { export_changed: true } : {}),
        ...(flags.spanInexact ? { span_inexact: true } : {}),
        ...(flags.gitRenameApplied ? { git_rename_applied: true } : {}),
        ...(flags.ambiguityRefused ? { ambiguity_refused: true } : {}),
        ...(flags.evidenceUnavailable ? { evidence_unavailable: true } : {}),
        ...(flags.boundHit ? { bound_hit: true } : {}),
        ...(flags.birthAtWindowBoundary ? { birth_at_window_boundary: true } : {}),
        ...(flags.semanticBasis ? { semantic_basis: flags.semanticBasis } : {}),
    };
}
function changeClass(change) {
    switch (change.changeType) {
        case 'added':
            return 'added';
        case 'deleted':
            return 'removed';
        case 'moved':
            return 'moved';
        case 'renamed':
            return 'renamed';
        case 'reordered':
            return 'reordered';
        case 'modified_logic':
            return 'logic';
        case 'modified_cosmetic':
            return 'cosmetic';
        default:
            return change.flags.logicChanged ? 'logic' : change.flags.cosmeticOnly ? 'cosmetic' : 'unknown';
    }
}
function semanticClass(change) {
    if (change.changeType === 'added' || change.changeType === 'deleted' || change.flags.logicChanged)
        return 'logic';
    if (change.flags.cosmeticOnly || change.changeType === 'modified_cosmetic')
        return 'cosmetic';
    if (change.changeType === 'moved' || change.changeType === 'renamed' || change.changeType === 'reordered') {
        return 'neutral';
    }
    return 'unknown';
}
function oidForRef(ref, side, changes) {
    if (side === 'before') {
        return changes.find(change => (change.oldPath ?? change.path) === ref.relativePath)?.oldBlobSha;
    }
    return changes.find(change => change.path === ref.relativePath)?.newBlobSha;
}
class BlobMaterializer {
    options;
    buffers;
    budget;
    decoded = new Map();
    constructor(options, buffers, budget) {
        this.options = options;
        this.buffers = buffers;
        this.budget = budget;
    }
    async acquire(oids, diagnostics) {
        const missing = [...new Set(oids.filter(oid => FULL_OID.test(oid) && !this.buffers.has(oid)))];
        if (missing.length === 0)
            return;
        const headers = await readGitObjectHeaders(missing, {
            repoRoot: this.options.repoRoot,
            signal: this.options.signal,
            physicalObjects: true,
        });
        const wanted = [];
        for (const oid of missing) {
            const header = headers.get(oid);
            if (!header || header.missing || header.type !== 'blob') {
                diagnostics.push(payloadDiagnostic(diagnostic('blob-unavailable', `Object ${oid} is not an available Git blob.`, { severity: 'error' })));
                continue;
            }
            if (header.size > this.options.bounds.maxBlobBytes) {
                diagnostics.push(payloadDiagnostic(diagnostic('blob-too-large', `Blob ${oid} exceeds the ${this.options.bounds.maxBlobBytes}-byte limit.`, {
                    severity: 'error',
                })));
                continue;
            }
            const alreadyCounted = this.budget.seenOids.has(oid);
            if (!alreadyCounted && this.budget.bytesRead + header.size > this.options.bounds.maxTotalBlobBytes) {
                diagnostics.push(payloadDiagnostic(diagnostic('blob-budget-exhausted', 'The total atom blob byte budget was exhausted.', {
                    severity: 'error',
                })));
                continue;
            }
            if (!alreadyCounted) {
                this.budget.bytesRead += header.size;
                this.budget.seenOids.add(oid);
            }
            wanted.push(oid);
        }
        const read = await readGitObjects(wanted, headers, {
            repoRoot: this.options.repoRoot,
            signal: this.options.signal,
            physicalObjects: true,
            maxBatchBytes: this.options.bounds.maxBatchBytes,
        });
        for (const [oid, buffer] of read)
            this.buffers.set(oid, buffer);
    }
    coordinate(ref, oid, exact, diagnostics) {
        if (!oid) {
            diagnostics.push(payloadDiagnostic(diagnostic('coordinate-unavailable', `No immutable blob coordinate was found for ${ref.relativePath}.`, {
                severity: 'error',
                path: ref.relativePath,
            })));
            return null;
        }
        const buffer = this.buffers.get(oid);
        if (!buffer)
            return null;
        let text = this.decoded.get(oid);
        if (text === undefined) {
            try {
                text = FATAL_UTF8.decode(buffer);
                this.decoded.set(oid, text);
            }
            catch {
                diagnostics.push(payloadDiagnostic(diagnostic('invalid-utf8', `Blob ${oid} is not valid UTF-8.`, {
                    severity: 'error',
                    path: ref.relativePath,
                })));
                return null;
            }
        }
        const lineCount = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n').length;
        if (lineCount > this.options.bounds.maxBlobLines) {
            diagnostics.push(payloadDiagnostic(diagnostic('blob-too-many-lines', `Blob ${oid} exceeds the ${this.options.bounds.maxBlobLines}-line limit.`, {
                severity: 'error',
                path: ref.relativePath,
            })));
            return null;
        }
        const span = selectGitCoordinateSpan(text, { start_line: ref.startLine, end_line: ref.endLine });
        if (!span) {
            diagnostics.push(payloadDiagnostic(diagnostic('span-unavailable', `The entity span for ${ref.relativePath} falls outside blob ${oid}.`, {
                severity: 'error',
                path: ref.relativePath,
            })));
            return null;
        }
        return {
            oid,
            path: ref.relativePath,
            span: { start_line: ref.startLine, end_line: ref.endLine, exact },
            span_digest: computeGitSpanDigest(span.source),
        };
    }
}
function coordinateEvidenceKey(coordinate) {
    if (!coordinate)
        return 'unavailable';
    return [
        coordinate.oid,
        coordinate.path,
        coordinate.span.start_line,
        coordinate.span.end_line,
        coordinate.span.exact ? 1 : 0,
        coordinate.span_digest,
    ].join('\0');
}
function stateKeyForRef(ref, diagnostics) {
    const ordinal = entityRefOrdinalFor(ref);
    if (ordinal === undefined) {
        diagnostics.push(payloadDiagnostic(diagnostic('lineage-state-key-unavailable', 'The extractor ordinal was unavailable for DAG correlation.', {
            severity: 'error',
            path: ref.relativePath,
        })));
        return undefined;
    }
    return entityStateKey({
        relativePath: ref.relativePath,
        kind: ref.kind,
        qualifiedName: ref.qualifiedName ?? ref.name,
        ordinal,
    });
}
function patchPayloadMeasures(patch, tokenizer) {
    let offset = 0;
    let inHunk = false;
    let bytes = 0;
    let lines = 0;
    let coverage = 'complete';
    const payloads = [];
    while (offset < patch.length) {
        const newline = patch.indexOf(0x0a, offset);
        const end = newline === -1 ? patch.length : newline + 1;
        const line = patch.subarray(offset, end);
        const ascii = line.toString('ascii');
        if (ascii.startsWith('@@'))
            inHunk = true;
        else if (ascii.startsWith('diff'))
            inHunk = false;
        if (ascii.startsWith('GIT binary patch') || ascii.startsWith('Binary files '))
            coverage = 'partial';
        if (inHunk && (line[0] === 0x2b || line[0] === 0x2d)) {
            const payload = line.subarray(1);
            bytes += payload.length;
            lines++;
            payloads.push(payload);
        }
        offset = end;
    }
    let tokens = 0;
    try {
        tokens = tokenizer.count(FATAL_UTF8.decode(Buffer.concat(payloads)));
    }
    catch {
        coverage = 'partial';
    }
    return { tokens, bytes, lines, coverage };
}
async function stablePatchId(patch, options) {
    if (patch.length === 0)
        return null;
    const result = await runGitRaw(['patch-id', '--stable'], {
        repoRoot: options.repoRoot,
        signal: options.signal,
        input: patch,
        maxBuffer: 1024 * 1024,
        physicalObjects: true,
    });
    const id = result.stdout.toString('ascii').trim().split(/\s+/)[0];
    return id && FULL_OID.test(id) ? id : null;
}
async function diffPatch(base, child, options) {
    const result = await runGitRaw(['diff', '--no-color', '--no-ext-diff', '--no-textconv', '--full-index', '--binary', base, child], {
        repoRoot: options.repoRoot,
        signal: options.signal,
        maxBuffer: 512 * 1024 * 1024,
        physicalObjects: true,
    });
    return result.stdout;
}
async function patchEvidence(base, child, tokenizer, options, diagnostics) {
    try {
        const [patch, reverse] = await Promise.all([diffPatch(base, child, options), diffPatch(child, base, options)]);
        const [patchId, reversePatchId] = await Promise.all([
            stablePatchId(patch, options),
            stablePatchId(reverse, options),
        ]);
        return {
            patchId,
            reversePatchId,
            measures: patchPayloadMeasures(patch, tokenizer),
            complete: patch.length === 0 || patchId !== null,
        };
    }
    catch (error) {
        diagnostics.push(payloadDiagnostic(diagnostic('patch-unavailable', error instanceof Error ? error.message : String(error), { severity: 'error' })));
        return {
            patchId: null,
            reversePatchId: null,
            measures: { tokens: 0, bytes: 0, lines: 0, coverage: 'unavailable' },
            complete: false,
        };
    }
}
function omittedPatchEvidence() {
    return {
        patchId: null,
        reversePatchId: null,
        measures: { tokens: 0, bytes: 0, lines: 0, coverage: 'unavailable' },
        complete: false,
    };
}
async function emptyTreeOid(options) {
    const result = await runGit(['hash-object', '-t', 'tree', '--stdin'], {
        repoRoot: options.repoRoot,
        signal: options.signal,
        input: '',
        physicalObjects: true,
    });
    const oid = result.stdout.trim();
    if (!FULL_OID.test(oid))
        throw new Error('Git did not return a valid empty-tree object id.');
    return oid;
}
function moduleLabel(relativePath) {
    return moduleForRelativePath(relativePath) || '.';
}
function modulePairKey(moduleA, moduleB) {
    return `module-pair:${[moduleA, moduleB].sort().map(encodeURIComponent).join('::')}`;
}
function touchedModulePairs(changes, bounds, diagnostics) {
    const paths = [
        ...new Set(changes.flatMap(change => [change.oldPath, change.path].filter((value) => !!value))),
    ].sort();
    let coverage = 'complete';
    const retainedPaths = paths.slice(0, bounds.maxFilesPerCommit);
    if (retainedPaths.length < paths.length) {
        coverage = 'partial';
        diagnostics.push(payloadDiagnostic(diagnostic('module-pairs-truncated', `Retained ${retainedPaths.length} of ${paths.length} changed paths for module-pair projection.`, { severity: 'error' })));
    }
    const modules = [...new Set(retainedPaths.map(moduleLabel))].sort();
    const pairs = [];
    for (let left = 0; left < modules.length; left++) {
        for (let right = left + 1; right < modules.length; right++) {
            const moduleA = modules[left];
            const moduleB = modules[right];
            if (!moduleA || !moduleB || moduleA === moduleB)
                continue;
            const [first, second] = [moduleA, moduleB].sort();
            pairs.push({ key: modulePairKey(first, second), module_a: first, module_b: second });
        }
    }
    return { count: pairs.length, modules, pairs, basis: 'changed-directory-module-pairs', coverage };
}
function entityDiagnosticPayload(source, parentRef, childRef) {
    return payloadDiagnostic(diagnostic(source.code, source.message, {
        severity: isEntityEvidenceGapDiagnostic(source) ? 'error' : 'warning',
        parentRef,
        childRef,
        ...(source.relativePath ? { path: source.relativePath } : {}),
    }));
}
async function buildTransition(commit, parentRef, parentIndex, base, tokenizer, materializer, cache, recognizer, options, repoIdentity, bodyCache, materializationBudget) {
    const diagnostics = [];
    const unchangedMatches = [];
    const rawChanges = await readRawDiff([base, commit.oid], {
        repoRoot: options.repoRoot,
        signal: options.signal,
    });
    const entityRawChanges = rawChanges.slice(0, options.bounds.maxFilesPerCommit);
    const context = {
        repoRoot: options.repoRoot,
        repoIdentity,
        recognizer,
        bounds: options.bounds,
        thresholds: options.thresholds,
        cache,
        formats: options.formats,
        bodyCache,
        materializationBudget,
        immutableTransition: {
            range: {
                base,
                head: commit.oid,
                kind: 'two-dot',
                resolvedBase: base,
                resolvedHead: commit.oid,
                describe: `${base}..${commit.oid}`,
            },
            changes: entityRawChanges,
        },
        observeUnchangedMatch: (previous, current) => unchangedMatches.push({ previous, current }),
        signal: options.signal,
    };
    const retainedPaths = new Set(entityRawChanges.flatMap(change => [change.path, change.oldPath].filter((value) => !!value)));
    if (rawChanges.length > options.bounds.maxFilesPerCommit) {
        diagnostics.push(payloadDiagnostic(diagnostic('commit-files-truncated', `Retained ${options.bounds.maxFilesPerCommit} of ${rawChanges.length} changed files for entity observation.`, { severity: 'error', parentRef, childRef: commit.oid })));
    }
    const diff = await computeEntityDiff({
        base,
        head: commit.oid,
        rangeMode: 'exact',
        maxEntities: options.bounds.maxEntitiesTotal,
        formats: options.formats,
    }, context);
    if (diff.status === 'error') {
        diagnostics.push(payloadDiagnostic(diagnostic(diff.code ?? 'entity-diff-failed', diff.message ?? 'Entity diff failed.', {
            severity: 'error',
            parentRef,
            childRef: commit.oid,
        })));
    }
    diagnostics.push(...diff.diagnostics.map(item => entityDiagnosticPayload(item, parentRef, commit.oid)));
    const visibleChanges = diff.changes.filter(change => {
        const paths = [change.previous?.relativePath, change.current?.relativePath].filter((value) => !!value);
        return paths.some(value => retainedPaths.has(value));
    });
    const requestedOids = visibleChanges.flatMap(change => {
        const before = change.previous ? oidForRef(change.previous, 'before', rawChanges) : undefined;
        const after = change.current ? oidForRef(change.current, 'after', rawChanges) : undefined;
        return [before, after].filter((value) => !!value);
    });
    for (const match of unchangedMatches) {
        const before = oidForRef(match.previous, 'before', rawChanges);
        const after = oidForRef(match.current, 'after', rawChanges);
        if (before)
            requestedOids.push(before);
        if (after)
            requestedOids.push(after);
    }
    await materializer.acquire(requestedOids, diagnostics);
    let stateKeyGap = false;
    const entities = visibleChanges.map(change => {
        const beforeOid = change.previous ? oidForRef(change.previous, 'before', rawChanges) : undefined;
        const afterOid = change.current ? oidForRef(change.current, 'after', rawChanges) : undefined;
        const exact = !change.flags.spanInexact;
        const before = change.previous ? materializer.coordinate(change.previous, beforeOid, exact, diagnostics) : null;
        const after = change.current ? materializer.coordinate(change.current, afterOid, exact, diagnostics) : null;
        const beforeStateKey = change.previous ? stateKeyForRef(change.previous, diagnostics) : undefined;
        const afterStateKey = change.current ? stateKeyForRef(change.current, diagnostics) : undefined;
        if ((change.previous && !beforeStateKey) || (change.current && !afterStateKey))
            stateKeyGap = true;
        return {
            change,
            before,
            after,
            ...(beforeStateKey ? { beforeStateKey } : {}),
            ...(afterStateKey ? { afterStateKey } : {}),
        };
    });
    const entityGap = diff.status === 'error' ||
        rawChanges.length > options.bounds.maxFilesPerCommit ||
        diff.diagnostics.some(item => isEntityEvidenceGapDiagnostic(item)) ||
        entities.some(entity => (!!entity.change.previous && !entity.before) || (!!entity.change.current && !entity.after));
    const continuityLinks = [];
    let continuityGap = false;
    for (const match of unchangedMatches) {
        const beforeOid = oidForRef(match.previous, 'before', rawChanges);
        const afterOid = oidForRef(match.current, 'after', rawChanges);
        const before = materializer.coordinate(match.previous, beforeOid, match.previous.spanExact, diagnostics);
        const after = materializer.coordinate(match.current, afterOid, match.current.spanExact, diagnostics);
        if (!before || !after) {
            continuityGap = true;
            continue;
        }
        const beforeStateKey = entityStateKey(match.previous);
        const afterStateKey = entityStateKey(match.current);
        if (beforeStateKey === afterStateKey && coordinateEvidenceKey(before) === coordinateEvidenceKey(after))
            continue;
        continuityLinks.push({
            beforeStateKey,
            afterStateKey,
            before,
            after,
        });
    }
    const patch = tokenizer
        ? await patchEvidence(base, commit.oid, tokenizer, options, diagnostics)
        : omittedPatchEvidence();
    const modulePairs = touchedModulePairs(rawChanges, options.bounds, diagnostics);
    return {
        commit,
        parentRef,
        parentIndex,
        rawChanges,
        entities,
        continuityLinks,
        patch,
        modulePairs,
        diagnostics,
        entityCoverage: entityGap ? 'partial' : 'complete',
        lineageCoverage: entityGap || continuityGap || stateKeyGap ? 'partial' : 'complete',
        revertOf: null,
    };
}
class UnionFind {
    parent = new Map();
    add(value) {
        if (!this.parent.has(value))
            this.parent.set(value, value);
    }
    find(value) {
        this.add(value);
        const parent = this.parent.get(value);
        if (parent === value)
            return value;
        const root = this.find(parent);
        this.parent.set(value, root);
        return root;
    }
    union(left, right) {
        const leftRoot = this.find(left);
        const rightRoot = this.find(right);
        if (leftRoot === rightRoot)
            return;
        const [first, second] = [leftRoot, rightRoot].sort();
        this.parent.set(second, first);
    }
    components() {
        const components = new Map();
        for (const value of this.parent.keys()) {
            const root = this.find(value);
            const members = components.get(root);
            if (members)
                members.push(value);
            else
                components.set(root, [value]);
        }
        for (const members of components.values())
            members.sort();
        return components;
    }
}
function stateKeyPath(stateKey) {
    const separator = stateKey.indexOf('\0');
    return separator === -1 ? stateKey : stateKey.slice(0, separator);
}
function assignLineages(transitions, runId, resolvedRef) {
    const union = new UnionFind();
    const firstParentByCommit = new Map();
    const transitionsByCommit = new Map();
    for (const transition of transitions) {
        const firstParent = transition.commit.parents[0];
        if (firstParent)
            firstParentByCommit.set(transition.commit.oid, firstParent);
        const grouped = transitionsByCommit.get(transition.commit.oid);
        if (grouped)
            grouped.push(transition);
        else
            transitionsByCommit.set(transition.commit.oid, [transition]);
    }
    const deltas = new Map();
    const pathBarriers = new Map();
    const lookupMemo = new Map();
    const boundaryStates = new Map();
    const materializedStates = new Map();
    const tokenFor = (role, ref, stateKey, coordinate, salt = '') => {
        const token = `state:${digest(canonicalJson({ role, ref, stateKey, coordinate: coordinateEvidenceKey(coordinate), salt }))}`;
        union.add(token);
        return token;
    };
    const lookup = (commitRef, stateKey) => {
        if (!commitRef)
            return undefined;
        const visited = [];
        let current = commitRef;
        let found = null;
        while (current) {
            const memoKey = `${current}\0${stateKey}`;
            if (lookupMemo.has(memoKey)) {
                found = lookupMemo.get(memoKey) ?? null;
                break;
            }
            const delta = deltas.get(current);
            if (delta?.has(stateKey)) {
                found = delta.get(stateKey) ?? null;
                break;
            }
            if (pathBarriers.get(current)?.has(stateKeyPath(stateKey))) {
                found = null;
                break;
            }
            visited.push(current);
            current = firstParentByCommit.get(current);
        }
        for (const ref of visited)
            lookupMemo.set(`${ref}\0${stateKey}`, found);
        return found ?? undefined;
    };
    const boundaryState = (parentRef, stateKey, coordinate) => {
        const key = `${parentRef ?? 'root'}\0${stateKey}\0${coordinateEvidenceKey(coordinate)}`;
        const existing = boundaryStates.get(key);
        if (existing)
            return existing;
        const created = {
            token: tokenFor('window-boundary', parentRef ?? 'root', stateKey, coordinate),
            coordinate,
        };
        boundaryStates.set(key, created);
        return created;
    };
    const fromParent = (parentRef, stateKey, coordinate) => lookup(parentRef, stateKey) ?? boundaryState(parentRef, stateKey, coordinate);
    const setChild = (childRef, stateKey, value) => {
        let delta = deltas.get(childRef);
        if (!delta) {
            delta = new Map();
            deltas.set(childRef, delta);
        }
        delta.set(stateKey, value);
        lookupMemo.delete(`${childRef}\0${stateKey}`);
        materializedStates.delete(childRef);
    };
    const materialize = (commitRef) => {
        const cached = materializedStates.get(commitRef);
        if (cached)
            return cached;
        const chain = [];
        let current = commitRef;
        let base;
        while (current) {
            const existing = materializedStates.get(current);
            if (existing) {
                base = new Map(existing);
                break;
            }
            chain.push(current);
            current = firstParentByCommit.get(current);
        }
        const state = base ?? new Map();
        for (const ref of chain.reverse()) {
            const barriers = pathBarriers.get(ref);
            if (barriers?.size) {
                for (const key of [...state.keys()]) {
                    if (barriers.has(stateKeyPath(key)))
                        state.delete(key);
                }
            }
            for (const [key, value] of deltas.get(ref) ?? []) {
                if (value)
                    state.set(key, value);
                else
                    state.delete(key);
            }
        }
        materializedStates.set(commitRef, state);
        while (materializedStates.size > 8) {
            const oldest = materializedStates.keys().next().value;
            if (!oldest)
                break;
            materializedStates.delete(oldest);
        }
        return state;
    };
    const primaryTransition = (group) => group.find(transition => transition.parentIndex === null || transition.parentIndex === 0);
    for (const commit of [...transitionsByCommit.values()].map(group => group[0]?.commit).filter(Boolean)) {
        const group = transitionsByCommit.get(commit.oid) ?? [];
        const primary = primaryTransition(group);
        if (!primary)
            continue;
        deltas.set(commit.oid, new Map());
        if (primary.lineageCoverage !== 'complete') {
            pathBarriers.set(commit.oid, new Set(primary.rawChanges.flatMap(change => [change.oldPath, change.path].filter((value) => !!value))));
        }
        // All reads resolve against the parent state, but writes land in one
        // shared child delta. A key can be vacated by one migration and occupied
        // by another in the same commit (rename chains A→B beside B→C, or a
        // delete beside a rename onto the deleted key), so applying each item's
        // delete-then-set in sequence would let a later delete clobber an earlier
        // set. Stage every write, then apply all vacating deletes before any set.
        const vacated = [];
        const occupied = [];
        for (const link of primary.continuityLinks) {
            const previous = fromParent(primary.parentRef, link.beforeStateKey, link.before);
            if (link.beforeStateKey !== link.afterStateKey)
                vacated.push(link.beforeStateKey);
            occupied.push([link.afterStateKey, { token: previous.token, coordinate: link.after }]);
        }
        const paired = primary.entities.filter(entity => entity.change.previous && entity.change.current && entity.beforeStateKey && entity.afterStateKey);
        for (const entity of paired) {
            const previous = fromParent(primary.parentRef, entity.beforeStateKey, entity.before);
            if (entity.beforeStateKey !== entity.afterStateKey)
                vacated.push(entity.beforeStateKey);
            occupied.push([entity.afterStateKey, { token: previous.token, coordinate: entity.after }]);
            entity.lineageToken = previous.token;
        }
        const deleted = primary.entities.filter(entity => entity.change.previous && !entity.change.current && entity.beforeStateKey);
        for (const entity of deleted) {
            const previous = fromParent(primary.parentRef, entity.beforeStateKey, entity.before);
            entity.lineageToken = previous.token;
            vacated.push(entity.beforeStateKey);
        }
        const added = primary.entities.filter(entity => !entity.change.previous && entity.change.current && entity.afterStateKey);
        for (const [index, entity] of added.entries()) {
            const stateKey = entity.afterStateKey;
            const token = tokenFor('birth', commit.oid, stateKey, entity.after, String(index));
            entity.lineageToken = token;
            occupied.push([stateKey, { token, coordinate: entity.after }]);
        }
        for (const stateKey of vacated)
            setChild(commit.oid, stateKey, null);
        for (const [stateKey, value] of occupied)
            setChild(commit.oid, stateKey, value);
        for (const [index, entity] of primary.entities.entries()) {
            if (entity.lineageToken)
                continue;
            entity.lineageToken = tokenFor('unresolved-state', commit.oid, entity.afterStateKey ?? entity.beforeStateKey ?? 'unavailable', entity.after ?? entity.before, String(index));
        }
        for (const secondary of group.filter(transition => transition !== primary)) {
            for (const link of secondary.continuityLinks) {
                const previous = fromParent(secondary.parentRef, link.beforeStateKey, link.before);
                let current = lookup(commit.oid, link.afterStateKey);
                if (!current) {
                    current = {
                        token: tokenFor('merge-state', commit.oid, link.afterStateKey, link.after),
                        coordinate: link.after,
                    };
                    setChild(commit.oid, link.afterStateKey, current);
                }
                union.union(previous.token, current.token);
            }
            for (const [index, entity] of secondary.entities.entries()) {
                if (entity.beforeStateKey && entity.afterStateKey) {
                    const previous = fromParent(secondary.parentRef, entity.beforeStateKey, entity.before);
                    let current = lookup(commit.oid, entity.afterStateKey);
                    if (!current) {
                        current = {
                            token: tokenFor('merge-state', commit.oid, entity.afterStateKey, entity.after, String(index)),
                            coordinate: entity.after,
                        };
                        setChild(commit.oid, entity.afterStateKey, current);
                    }
                    union.union(previous.token, current.token);
                    entity.lineageToken = current.token;
                }
                else if (entity.afterStateKey) {
                    let current = lookup(commit.oid, entity.afterStateKey);
                    if (!current) {
                        current = {
                            token: tokenFor('merge-birth', commit.oid, entity.afterStateKey, entity.after, String(index)),
                            coordinate: entity.after,
                        };
                        setChild(commit.oid, entity.afterStateKey, current);
                    }
                    entity.lineageToken = current.token;
                }
                else if (entity.beforeStateKey) {
                    entity.lineageToken = fromParent(secondary.parentRef, entity.beforeStateKey, entity.before).token;
                }
                else {
                    entity.lineageToken = tokenFor('unresolved-merge-state', commit.oid, 'unavailable', entity.after ?? entity.before, String(index));
                }
            }
        }
        if (commit.parents.length > 1) {
            const childState = materialize(commit.oid);
            const childByEvidence = new Map();
            for (const [stateKey, active] of childState) {
                if (!active.coordinate)
                    continue;
                const key = `${stateKey}\0${coordinateEvidenceKey(active.coordinate)}`;
                const values = childByEvidence.get(key);
                if (values)
                    values.push(active);
                else
                    childByEvidence.set(key, [active]);
            }
            for (const parentRef of commit.parents.slice(1)) {
                for (const [stateKey, active] of materialize(parentRef)) {
                    if (!active.coordinate)
                        continue;
                    const candidates = childByEvidence.get(`${stateKey}\0${coordinateEvidenceKey(active.coordinate)}`) ?? [];
                    if (candidates.length === 1 && candidates[0])
                        union.union(active.token, candidates[0].token);
                }
            }
        }
    }
    const components = union.components();
    const lineageByToken = new Map();
    for (const members of components.values()) {
        const lineageId = `dag1:${digest(`${runId}\0${members.join('\0')}`).slice(0, 32)}`;
        for (const member of members)
            lineageByToken.set(member, lineageId);
    }
    for (const transition of transitions) {
        for (const entity of transition.entities) {
            if (!entity.lineageToken)
                throw new Error('Observed atom entity has no DAG lineage token.');
            entity.lineageId = lineageByToken.get(entity.lineageToken);
            if (!entity.lineageId)
                throw new Error('Observed atom entity has no DAG lineage assignment.');
        }
    }
    const heads = [];
    for (const [stateKey, active] of materialize(resolvedRef)) {
        if (!active.coordinate)
            continue;
        const lineageId = lineageByToken.get(active.token);
        if (lineageId)
            heads.push({ lineageId, coordinate: active.coordinate, stateKey });
    }
    return { lineageCount: components.size, heads };
}
function ancestorDistance(ancestor, descendant, byOid) {
    if (ancestor === descendant)
        return 0;
    const queue = [{ oid: descendant, distance: 0 }];
    const seen = new Set([descendant]);
    for (let index = 0; index < queue.length; index++) {
        const current = queue[index];
        if (!current)
            continue;
        for (const parent of byOid.get(current.oid)?.parents ?? []) {
            if (parent === ancestor)
                return current.distance + 1;
            if (seen.has(parent))
                continue;
            seen.add(parent);
            queue.push({ oid: parent, distance: current.distance + 1 });
        }
    }
    return undefined;
}
function detectRevert(transition, patches, byOid) {
    if (!transition.parentRef || !transition.patch.reversePatchId)
        return null;
    const candidates = patches.get(transition.patch.reversePatchId);
    if (!candidates)
        return null;
    const reachable = candidates
        .map(candidate => ({
        candidate,
        distance: ancestorDistance(candidate.commit.oid, transition.parentRef, byOid),
    }))
        .filter((item) => item.distance !== undefined)
        .sort((a, b) => a.distance - b.distance ||
        compareCodeUnits(a.candidate.commit.oid, b.candidate.commit.oid) ||
        (a.candidate.parentIndex ?? -1) - (b.candidate.parentIndex ?? -1));
    if (reachable.length === 0)
        return null;
    const nearest = reachable[0]?.distance;
    const atNearest = reachable.filter(item => item.distance === nearest);
    if (atNearest.length !== 1 || !atNearest[0])
        return null;
    const target = atNearest[0].candidate;
    return { child_ref: target.commit.oid, parent_ref: target.parentRef, parent_index: target.parentIndex };
}
function entityOrder(a, b) {
    const aRef = a.after ?? a.before;
    const bRef = b.after ?? b.before;
    return (compareCodeUnits(aRef?.path ?? '', bRef?.path ?? '') ||
        (aRef?.span.start_line ?? 0) - (bRef?.span.start_line ?? 0) ||
        compareCodeUnits(a.lineageId ?? '', b.lineageId ?? ''));
}
function inferMatchEvidence(change) {
    if (!['content-hash', 'normalized-hash', 'structural-hash'].includes(change.matchReason))
        return undefined;
    const pathChanged = change.previous?.relativePath !== change.current?.relativePath;
    const pool = pathChanged ? (change.flags.gitRenameApplied ? 'git-rename' : 'cross-file') : 'same-path';
    return {
        pool,
        hashes_comparable: true,
        ...(change.matchReason === 'content-hash' ? { body_hash_equal: true, exact_body_hash_equal: true } : {}),
        ...(change.matchReason === 'normalized-hash' ? { normalized_hash_equal: true } : {}),
        ...(change.matchReason === 'structural-hash' ? { structural_hash_equal: true } : {}),
    };
}
function toWireEntity(entity) {
    const evidence = inferMatchEvidence(entity.change);
    return {
        before: entity.before,
        after: entity.after,
        change_class: changeClass(entity.change),
        semantic_class: semanticClass(entity.change),
        lineage_id: entity.lineageId ?? '',
        match_reason: entity.change.matchReason,
        // Additions and removals have no accepted before/after match. Their
        // classification can be certain, but the matcher rung itself is `none`.
        // Match presence is the record pair, not the coordinates: an evidence gap
        // can null a coordinate while the accepted match keeps its confidence.
        match_confidence: entity.change.previous && entity.change.current ? entity.change.confidence : 0,
        ...(evidence ? { match_evidence: evidence } : {}),
        flags: flagsToWire(entity.change.flags),
    };
}
function coverageMerge(current, next) {
    if (current === 'unavailable' || next === 'unavailable')
        return 'unavailable';
    if (current === 'partial' || next === 'partial')
        return 'partial';
    return 'complete';
}
function resumeMatches(cursor, transition) {
    return (cursor.child_ref === transition.commit.oid &&
        cursor.parent_ref === transition.parentRef &&
        cursor.parent_index === transition.parentIndex);
}
function makeCursor(history, transition, options, occVersion, lineageRunId) {
    const bounds = boundsToWire(options.bounds);
    const since_ref = history.resolvedSince ?? null;
    const options_fingerprint = cursorFingerprint({
        resolved_ref: history.resolvedHead,
        since_ref,
        tokenizer: options.tokenizer,
        bounds,
        formats: options.formats,
        lineage_run_id: lineageRunId,
        occ_version: occVersion,
        lineage_algorithm: LINEAGE_ALGORITHM,
        entity_parser: ENTITY_PARSER_CONTRACT_ID,
    });
    return encodeCursor({
        schema_version: ATOMS_SCHEMA_VERSION,
        resolved_ref: history.resolvedHead,
        since_ref,
        parent_ref: transition.parentRef,
        child_ref: transition.commit.oid,
        parent_index: transition.parentIndex,
        lineage_run_id: lineageRunId,
        occ_version: occVersion,
        lineage_algorithm: LINEAGE_ALGORITHM,
        entity_parser: ENTITY_PARSER_CONTRACT_ID,
        tokenizer: options.tokenizer,
        bounds,
        formats: options.formats,
        options_fingerprint,
    });
}
function candidateKey(candidate) {
    return `${candidate.coordinate.path}\0${candidate.coordinate.span.start_line}\0${candidate.coordinate.span.end_line}\0${candidate.coordinate.span_digest}`;
}
async function buildLineageHeads(candidates, resolvedRef, options) {
    let tree;
    try {
        tree = await listTreeBlobs(resolvedRef, { repoRoot: options.repoRoot, signal: options.signal });
    }
    catch (error) {
        // Mirror the entity-store arm below: a failed pinned-tree read degrades
        // the head mapping instead of aborting the stream without a footer.
        const message = error instanceof Error ? error.message : String(error);
        return {
            heads: [],
            coverage: 'unavailable',
            diagnostics: [diagnostic('head-mapping-unavailable', message, { severity: 'error' })],
        };
    }
    const treeByPath = new Map(tree.map(entry => [entry.path, entry.blobSha]));
    const byLineage = new Map();
    let coverage = 'complete';
    const projectionDiagnostics = [];
    let treeMismatches = 0;
    for (const candidate of candidates) {
        if (treeByPath.get(candidate.coordinate.path) !== candidate.coordinate.oid) {
            treeMismatches++;
            continue;
        }
        const grouped = byLineage.get(candidate.lineageId);
        if (grouped)
            grouped.push(candidate);
        else
            byLineage.set(candidate.lineageId, [candidate]);
    }
    if (treeMismatches > 0) {
        coverage = 'partial';
        projectionDiagnostics.push(diagnostic('head-coordinate-mismatch', `${treeMismatches} derived live head(s) did not bind to the pinned tree.`, {
            severity: 'error',
        }));
    }
    const selected = [...byLineage.values()]
        .map(candidates => [...candidates].sort((a, b) => compareCodeUnits(candidateKey(a), candidateKey(b)))[0])
        .filter((candidate) => !!candidate)
        .sort((a, b) => compareCodeUnits(a.lineageId, b.lineageId));
    let retained = selected;
    if (selected.length > options.bounds.maxLiveEntities) {
        retained = selected.slice(0, options.bounds.maxLiveEntities);
        coverage = 'partial';
        projectionDiagnostics.push(diagnostic('live-lineage-limit-exhausted', `Retained ${retained.length} of ${selected.length} pinned lineage heads.`, { severity: 'error' }));
    }
    let index;
    try {
        const store = openEntityStore({
            repoRoot: options.repoRoot,
            head: resolvedRef,
            bounds: options.bounds,
            thresholds: options.thresholds,
        });
        try {
            index = await store.index({ signal: options.signal });
        }
        finally {
            store.dispose();
        }
    }
    catch (error) {
        coverage = 'unavailable';
        const message = error instanceof Error ? error.message : String(error);
        return {
            heads: retained.map(candidate => ({
                schema_version: ATOMS_SCHEMA_VERSION,
                type: 'lineage-head',
                lineage_id: candidate.lineageId,
                coordinate: candidate.coordinate,
                entity_lineage_id: null,
                resolution: 'unavailable',
                diagnostics: [payloadDiagnostic(diagnostic('head-mapping-unavailable', message, { severity: 'error' }))],
            })),
            coverage,
            diagnostics: projectionDiagnostics,
        };
    }
    const indexGaps = index.status.diagnostics.filter(item => isEntityEvidenceGapDiagnostic(item));
    if (indexGaps.length > 0) {
        coverage = 'partial';
        projectionDiagnostics.push(diagnostic('head-mapping-index-incomplete', `The pinned first-parent index reported evidence gaps: ${[...new Set(indexGaps.map(item => item.code))].join(', ')}.`, { severity: 'error' }));
    }
    const lineagesByStateKey = new Map();
    for (const head of index.state.byLineageId.values()) {
        const key = entityStateKey(head.record);
        const values = lineagesByStateKey.get(key);
        if (values)
            values.push(head.lineageId);
        else
            lineagesByStateKey.set(key, [head.lineageId]);
    }
    const heads = retained.map(candidate => {
        const matches = lineagesByStateKey.get(candidate.stateKey) ?? [];
        if (matches.length === 1 && matches[0]) {
            return {
                schema_version: ATOMS_SCHEMA_VERSION,
                type: 'lineage-head',
                lineage_id: candidate.lineageId,
                coordinate: candidate.coordinate,
                entity_lineage_id: matches[0],
                resolution: 'resolved',
                diagnostics: [],
            };
        }
        coverage = 'partial';
        const ambiguous = matches.length > 1;
        return {
            schema_version: ATOMS_SCHEMA_VERSION,
            type: 'lineage-head',
            lineage_id: candidate.lineageId,
            coordinate: candidate.coordinate,
            entity_lineage_id: null,
            resolution: ambiguous ? 'ambiguous' : 'not_found',
            diagnostics: [
                payloadDiagnostic(diagnostic(ambiguous ? 'head-mapping-ambiguous' : 'head-mapping-not-found', ambiguous
                    ? 'The pinned entity state key resolved to multiple first-parent lineages.'
                    : 'The pinned entity state key was not present in the first-parent entity index.', { severity: 'error', path: candidate.coordinate.path })),
            ],
        };
    });
    return { heads, coverage, diagnostics: projectionDiagnostics };
}
async function* emitAtomsWithPolicy(input, includePatchEvidence) {
    const options = resolveOptions(input);
    const history = await readGitDagHistory({
        repoRoot: options.repoRoot,
        ref: options.ref,
        ...(options.since ? { since: options.since } : {}),
        maxCommits: options.bounds.maxCommits,
        signal: options.signal,
    });
    const tokenizer = includePatchEvidence ? await createTokenizer(options.tokenizer) : undefined;
    const occVersion = await readOccVersion();
    if (options.resumeCursor && options.resumeCursor.occ_version !== occVersion) {
        throw new Error(`The atoms cursor was produced by OCC ${options.resumeCursor.occ_version}; this build is ${occVersion}. Start a new run.`);
    }
    // `since` changes output only. Correlation at one pin stays identical to a
    // full run with the same evidence policy and bounds.
    const runMaterial = canonicalJson({
        schema: ATOMS_SCHEMA_VERSION,
        lineage: LINEAGE_ALGORITHM,
        resolved_ref: history.resolvedHead,
        bounds: boundsToWire(options.bounds),
        thresholds: options.thresholds,
        formats: options.formats,
        occ_version: occVersion,
        entity_parser: ENTITY_PARSER_CONTRACT,
        history: history.commits.map(commit => ({ oid: commit.oid, parents: commit.parents })),
        shallow: history.shallow,
        truncated: history.truncated,
    });
    const lineageRunId = `atomrun2:${digest(runMaterial).slice(0, 32)}`;
    if (options.resumeCursor && options.resumeCursor.lineage_run_id !== lineageRunId) {
        throw new Error('The atoms cursor does not match the repository history evidence available for this run.');
    }
    const header = {
        schema_version: ATOMS_SCHEMA_VERSION,
        type: 'header',
        requested_ref: history.requestedRef,
        resolved_ref: history.resolvedHead,
        since_ref: history.resolvedSince ?? null,
        resumed_from: input.resume ?? null,
        object_format: history.objectFormat,
        shallow: history.shallow,
        lineage_run_id: lineageRunId,
        tokenizer: options.tokenizer,
        formats: options.formats,
        bounds: boundsToWire(options.bounds),
        lineage_thresholds: {
            similarity_min: options.thresholds.similarityMin,
            similarity_margin: options.thresholds.similarityMargin,
            similarity_min_tokens: options.thresholds.similarityMinTokens,
            similarity_length_ratio_min: options.thresholds.similarityLengthRatioMin,
            shingle_width: options.thresholds.shingleWidth,
        },
        provenance: {
            instrument: 'occ.atoms',
            instrument_version: ATOMS_SCHEMA_VERSION,
            occ_version: occVersion,
            node_version: process.versions.node,
        },
        policy: {
            topology: 'all-parent-edges',
            order: 'parents-before-children',
            replacements: 'disabled',
            grafts: 'disabled',
            lazy_fetch: 'disabled',
            source_bodies: 'omitted',
            span_digest: GIT_SPAN_DIGEST_ALGORITHM,
            lineage: LINEAGE_ALGORITHM,
            lineage_matcher: 'entity-evidence-ladder-v2',
            lineage_confidence: 'ordered-rung-score-not-probability',
            entity_parser: ENTITY_PARSER_CONTRACT_ID,
            module_pair_basis: 'changed-directory-module-pairs',
            native_identity: 'git-oid-path-span-digest',
            entity_lineage_mapping: 'pinned-first-parent-entity-index',
            checkout_node_ids: 'omitted',
            resume_computation: 'warm-replay',
        },
    };
    yield header;
    let diagnosticsCount = 0;
    if (history.shallow) {
        diagnosticsCount++;
        yield diagnostic('shallow-repository', 'Repository history is shallow; unreachable ancestors are unavailable.', {
            severity: 'error',
        });
    }
    if (history.truncated) {
        diagnosticsCount++;
        yield diagnostic('commit-window-truncated', `Retained the newest ${options.bounds.maxCommits} commits; older ancestors are outside the atom window.`, { severity: 'error' });
    }
    const emptyTree = await emptyTreeOid(options);
    const bodyCache = new Map();
    const materializationBudget = { bytesRead: 0, seenOids: new Set() };
    const materializer = new BlobMaterializer(options, bodyCache, materializationBudget);
    const entityCache = createMemoryBlobCache();
    const recognizer = createTestRecognizer();
    const commitByOid = new Map(history.commits.map(commit => [commit.oid, commit]));
    const repoIdentity = `atoms:${history.commits
        .filter(commit => commit.parents.length === 0)
        .map(commit => commit.oid)
        .sort()
        .join(',') || history.resolvedHead}`;
    const transitions = [];
    let globalEntityCount = 0;
    let globalLineageEventCount = 0;
    let lineageEventsTruncated = false;
    for (const commit of history.commits) {
        const parentEntries = commit.parents.length === 0
            ? [{ parentRef: null, parentIndex: null, base: emptyTree }]
            : commit.parents.map((parentRef, parentIndex) => ({ parentRef, parentIndex, base: parentRef }));
        for (const parent of parentEntries) {
            const transition = await buildTransition(commit, parent.parentRef, parent.parentIndex, parent.base, tokenizer, materializer, entityCache, recognizer, options, repoIdentity, bodyCache, materializationBudget);
            const remainingEntities = Math.max(0, options.bounds.maxEntitiesTotal - globalEntityCount);
            const remainingLineageEvents = Math.max(0, options.bounds.maxLineageEvents - globalLineageEventCount);
            const remaining = Math.min(remainingEntities, remainingLineageEvents);
            if (transition.entities.length > remaining) {
                const omitted = transition.entities.length - remaining;
                transition.entities = transition.entities.slice(0, remaining);
                transition.entityCoverage = 'partial';
                transition.lineageCoverage = 'partial';
                if (remainingEntities <= remainingLineageEvents) {
                    transition.diagnostics.push(payloadDiagnostic(diagnostic('entity-budget-exhausted', `The global atom entity bound omitted ${omitted} transition entities.`, { severity: 'error', parentRef: transition.parentRef, childRef: transition.commit.oid })));
                }
                if (remainingLineageEvents <= remainingEntities) {
                    lineageEventsTruncated = true;
                    transition.diagnostics.push(payloadDiagnostic(diagnostic('event-budget-exhausted', `The global lineage-event bound omitted ${omitted} transition entities.`, { severity: 'error', parentRef: transition.parentRef, childRef: transition.commit.oid })));
                }
            }
            globalEntityCount += transition.entities.length;
            globalLineageEventCount += transition.entities.length;
            const remainingContinuity = Math.max(0, options.bounds.maxLineageEvents - globalLineageEventCount);
            if (transition.continuityLinks.length > remainingContinuity) {
                const omitted = transition.continuityLinks.length - remainingContinuity;
                transition.continuityLinks = transition.continuityLinks.slice(0, remainingContinuity);
                transition.lineageCoverage = 'partial';
                lineageEventsTruncated = true;
                transition.diagnostics.push(payloadDiagnostic(diagnostic('event-budget-exhausted', `The global lineage-event bound omitted ${omitted} unchanged continuity link(s).`, { severity: 'error', parentRef: transition.parentRef, childRef: transition.commit.oid })));
            }
            globalLineageEventCount += transition.continuityLinks.length;
            transitions.push(transition);
        }
    }
    const assignedLineages = assignLineages(transitions, lineageRunId, history.resolvedHead);
    const patchEdges = new Map();
    for (const transition of transitions) {
        transition.revertOf = detectRevert(transition, patchEdges, commitByOid);
        if (!transition.patch.patchId)
            continue;
        const edges = patchEdges.get(transition.patch.patchId);
        if (edges)
            edges.push(transition);
        else
            patchEdges.set(transition.patch.patchId, [transition]);
    }
    const headProjection = await buildLineageHeads(assignedLineages.heads, history.resolvedHead, options);
    for (const item of headProjection.diagnostics) {
        diagnosticsCount++;
        yield item;
    }
    let episodesEmitted = 0;
    let entitiesEmitted = 0;
    let resumeSeen = !options.resumeCursor;
    let entitiesCoverage = 'complete';
    let lineageObservationCoverage = 'complete';
    let measuresCoverage = includePatchEvidence ? 'complete' : 'unavailable';
    let patchesCoverage = includePatchEvidence ? 'complete' : 'unavailable';
    let modulePairsCoverage = 'complete';
    const shouldEmitByTransition = new Map();
    for (const transition of transitions) {
        let shouldEmit = history.emitCommits.has(transition.commit.oid);
        if (!resumeSeen && options.resumeCursor && resumeMatches(options.resumeCursor, transition)) {
            resumeSeen = true;
            shouldEmit = false;
        }
        else if (!resumeSeen) {
            shouldEmit = false;
        }
        shouldEmitByTransition.set(transition, shouldEmit);
        entitiesCoverage = coverageMerge(entitiesCoverage, transition.entityCoverage);
        lineageObservationCoverage = coverageMerge(lineageObservationCoverage, transition.lineageCoverage);
        if (includePatchEvidence) {
            measuresCoverage = coverageMerge(measuresCoverage, transition.patch.measures.coverage);
            if (!transition.patch.complete)
                patchesCoverage = coverageMerge(patchesCoverage, 'partial');
        }
        modulePairsCoverage = coverageMerge(modulePairsCoverage, transition.modulePairs.coverage);
    }
    if (!resumeSeen)
        throw new Error('The atoms resume cursor was not found in the pinned history and output scope.');
    // Diagnostics for warm-replayed or filtered transitions would otherwise be
    // invisible even though they affect the footer's completeness claim.
    for (const transition of transitions) {
        if (shouldEmitByTransition.get(transition))
            continue;
        for (const item of transition.diagnostics) {
            diagnosticsCount++;
            yield fullDiagnostic({
                ...item,
                ...(item.parent_ref === undefined ? { parent_ref: transition.parentRef } : {}),
                ...(item.child_ref === undefined ? { child_ref: transition.commit.oid } : {}),
            });
        }
    }
    for (const transition of transitions) {
        if (!shouldEmitByTransition.get(transition))
            continue;
        transition.entities.sort(entityOrder);
        const episode = {
            schema_version: ATOMS_SCHEMA_VERSION,
            type: 'episode',
            cursor: makeCursor(history, transition, options, occVersion, lineageRunId),
            parent_ref: transition.parentRef,
            child_ref: transition.commit.oid,
            parent_index: transition.parentIndex,
            observed_at: transition.commit.committedAt,
            message_ref: transition.commit.oid,
            author: {
                name: transition.commit.author.name,
                email: transition.commit.author.email,
                authored_at: transition.commit.author.time,
            },
            entities: transition.entities.map(toWireEntity),
            measures: {
                basis: 'git-patch-added-deleted-payload',
                ...transition.patch.measures,
                tokenizer: options.tokenizer,
            },
            patch_id: transition.patch.patchId,
            revert_of: transition.revertOf,
            module_pairs_touched: transition.modulePairs,
            coverage: {
                entities: transition.entityCoverage,
                lineages: transition.lineageCoverage,
                measures: transition.patch.measures.coverage,
                patches: transition.patch.complete ? 'complete' : 'partial',
                module_pairs: transition.modulePairs.coverage,
            },
            diagnostics: transition.diagnostics,
        };
        episodesEmitted++;
        entitiesEmitted += episode.entities.length;
        diagnosticsCount += transition.diagnostics.length;
        yield episode;
    }
    let lineageHeadDiagnostics = 0;
    for (const head of headProjection.heads) {
        lineageHeadDiagnostics += head.diagnostics.length;
        yield head;
    }
    diagnosticsCount += lineageHeadDiagnostics;
    const historyCoverage = history.shallow || history.truncated ? 'partial' : 'complete';
    const lineagesCoverage = !lineageEventsTruncated && entitiesCoverage === 'complete' && lineageObservationCoverage === 'complete'
        ? 'complete'
        : entitiesCoverage === 'unavailable'
            ? 'unavailable'
            : 'partial';
    const headMappingCoverage = coverageMerge(headProjection.coverage, lineagesCoverage);
    const complete = historyCoverage === 'complete' &&
        entitiesCoverage === 'complete' &&
        lineagesCoverage === 'complete' &&
        headMappingCoverage === 'complete' &&
        measuresCoverage === 'complete' &&
        patchesCoverage === 'complete' &&
        modulePairsCoverage === 'complete';
    yield {
        schema_version: ATOMS_SCHEMA_VERSION,
        type: 'footer',
        complete,
        coverage: {
            history: historyCoverage,
            entities: entitiesCoverage,
            lineages: lineagesCoverage,
            head_mapping: headMappingCoverage,
            measures: measuresCoverage,
            patches: patchesCoverage,
            module_pairs: modulePairsCoverage,
        },
        counts: {
            commits_observed: history.commits.length,
            transitions_observed: transitions.length,
            episodes_emitted: episodesEmitted,
            entities_emitted: entitiesEmitted,
            lineages_observed: assignedLineages.lineageCount,
            lineage_heads_emitted: headProjection.heads.length,
            diagnostics: diagnosticsCount,
        },
    };
}
export const OccAtomsRunV1Schema = z
    .object({
    header: AtomHeaderV1Schema,
    diagnostics: z.array(AtomDiagnosticV1Schema),
    episodes: z.array(AtomEpisodeV1Schema),
    lineage_heads: z.array(AtomLineageHeadV1Schema),
    footer: AtomFooterV1Schema,
})
    .strict()
    .superRefine((run, context) => {
    const transitionKeys = new Set();
    const cursors = new Set();
    for (const [index, episode] of run.episodes.entries()) {
        const key = `${episode.child_ref}\0${episode.parent_ref ?? ''}\0${episode.parent_index ?? -1}`;
        if (transitionKeys.has(key)) {
            context.addIssue({ code: 'custom', path: ['episodes', index], message: 'Duplicate transition episode.' });
        }
        transitionKeys.add(key);
        if (cursors.has(episode.cursor)) {
            context.addIssue({ code: 'custom', path: ['episodes', index, 'cursor'], message: 'Duplicate cursor.' });
        }
        cursors.add(episode.cursor);
        const cursor = decodeAtomsCursorV1(episode.cursor);
        if (cursor.resolved_ref !== run.header.resolved_ref ||
            cursor.since_ref !== run.header.since_ref ||
            cursor.child_ref !== episode.child_ref ||
            cursor.parent_ref !== episode.parent_ref ||
            cursor.parent_index !== episode.parent_index ||
            cursor.lineage_run_id !== run.header.lineage_run_id ||
            cursor.tokenizer !== run.header.tokenizer ||
            cursor.formats !== run.header.formats ||
            cursor.occ_version !== run.header.provenance.occ_version ||
            canonicalJson(cursor.bounds) !== canonicalJson(run.header.bounds)) {
            context.addIssue({
                code: 'custom',
                path: ['episodes', index, 'cursor'],
                message: 'Cursor does not bind episode.',
            });
        }
    }
    if (new Set(run.lineage_heads.map(head => head.lineage_id)).size !== run.lineage_heads.length) {
        context.addIssue({ code: 'custom', path: ['lineage_heads'], message: 'Lineage-head ids must be unique.' });
    }
    for (const [index, head] of run.lineage_heads.entries()) {
        if ((head.resolution === 'resolved') !== (head.entity_lineage_id !== null)) {
            context.addIssue({
                code: 'custom',
                path: ['lineage_heads', index, 'entity_lineage_id'],
                message: 'Only resolved head mappings may carry an entity lineage id.',
            });
        }
    }
    const entities = run.episodes.reduce((total, episode) => total + episode.entities.length, 0);
    const diagnostics = run.diagnostics.length +
        run.episodes.reduce((total, episode) => total + episode.diagnostics.length, 0) +
        run.lineage_heads.reduce((total, head) => total + head.diagnostics.length, 0);
    if (run.footer.counts.episodes_emitted !== run.episodes.length ||
        run.footer.counts.entities_emitted !== entities ||
        run.footer.counts.lineage_heads_emitted !== run.lineage_heads.length ||
        run.footer.counts.diagnostics !== diagnostics) {
        context.addIssue({ code: 'custom', path: ['footer', 'counts'], message: 'Atoms footer counts do not match.' });
    }
    const complete = Object.values(run.footer.coverage).every(value => value === 'complete');
    if (run.footer.complete !== complete) {
        context.addIssue({
            code: 'custom',
            path: ['footer', 'complete'],
            message: 'Atoms completeness is inconsistent.',
        });
    }
});
/** Consume through EOF and cross-check a whole atoms stream. */
export async function collectAtomsRunV1(events) {
    let header;
    let footer;
    let seenHead = false;
    const diagnostics = [];
    const episodes = [];
    const lineageHeads = [];
    for await (const raw of events) {
        const event = OccAtomsEventV1Schema.parse(raw);
        if (footer)
            throw new Error('Atoms event encountered after the footer.');
        if (!header) {
            if (event.type !== 'header')
                throw new Error('Atoms stream must begin with exactly one header.');
            header = event;
            continue;
        }
        if (event.type === 'header')
            throw new Error('Atoms stream contains a duplicate header.');
        if (event.type === 'footer') {
            footer = event;
            continue;
        }
        if (event.type === 'diagnostic') {
            diagnostics.push(event);
            continue;
        }
        if (event.type === 'lineage-head') {
            seenHead = true;
            lineageHeads.push(event);
            continue;
        }
        if (seenHead)
            throw new Error('Atoms episode encountered after lineage-head records.');
        episodes.push(event);
    }
    if (!header)
        throw new Error('Atoms stream is missing its header.');
    if (!footer)
        throw new Error('Atoms stream is missing its footer.');
    return OccAtomsRunV1Schema.parse({ header, diagnostics, episodes, lineage_heads: lineageHeads, footer });
}
export const OccAtomsRunV2Schema = z
    .object({
    header: AtomHeaderV2Schema,
    diagnostics: z.array(AtomDiagnosticV2Schema),
    episodes: z.array(AtomEpisodeV2Schema),
    lineage_heads: z.array(AtomLineageHeadV2Schema),
    footer: AtomFooterV2Schema,
})
    .strict()
    .superRefine((run, context) => {
    const transitionKeys = new Set();
    const cursors = new Set();
    for (const [index, episode] of run.episodes.entries()) {
        const key = `${episode.child_ref}\0${episode.parent_ref ?? ''}\0${episode.parent_index ?? -1}`;
        if (transitionKeys.has(key)) {
            context.addIssue({ code: 'custom', path: ['episodes', index], message: 'Duplicate transition episode.' });
        }
        transitionKeys.add(key);
        if (cursors.has(episode.cursor)) {
            context.addIssue({ code: 'custom', path: ['episodes', index, 'cursor'], message: 'Duplicate cursor.' });
        }
        cursors.add(episode.cursor);
        let cursor;
        try {
            cursor = decodeAtomsCursor(episode.cursor);
        }
        catch (error) {
            context.addIssue({
                code: 'custom',
                path: ['episodes', index, 'cursor'],
                message: error instanceof Error ? error.message : String(error),
            });
            continue;
        }
        if (cursor.resolved_ref !== run.header.resolved_ref ||
            cursor.since_ref !== run.header.since_ref ||
            cursor.child_ref !== episode.child_ref ||
            cursor.parent_ref !== episode.parent_ref ||
            cursor.parent_index !== episode.parent_index ||
            cursor.lineage_run_id !== run.header.lineage_run_id ||
            cursor.tokenizer !== run.header.tokenizer ||
            cursor.formats !== run.header.formats ||
            cursor.occ_version !== run.header.provenance.occ_version ||
            canonicalJson(cursor.bounds) !== canonicalJson(run.header.bounds)) {
            context.addIssue({
                code: 'custom',
                path: ['episodes', index, 'cursor'],
                message: 'Cursor does not bind episode.',
            });
        }
    }
    if (new Set(run.lineage_heads.map(head => head.lineage_id)).size !== run.lineage_heads.length) {
        context.addIssue({ code: 'custom', path: ['lineage_heads'], message: 'Lineage-head ids must be unique.' });
    }
    for (const [index, head] of run.lineage_heads.entries()) {
        if ((head.resolution === 'resolved') !== (head.entity_lineage_id !== null)) {
            context.addIssue({
                code: 'custom',
                path: ['lineage_heads', index, 'entity_lineage_id'],
                message: 'Only resolved head mappings may carry an entity lineage id.',
            });
        }
    }
    const entities = run.episodes.reduce((total, episode) => total + episode.entities.length, 0);
    const diagnostics = run.diagnostics.length +
        run.episodes.reduce((total, episode) => total + episode.diagnostics.length, 0) +
        run.lineage_heads.reduce((total, head) => total + head.diagnostics.length, 0);
    if (run.footer.counts.episodes_emitted !== run.episodes.length ||
        run.footer.counts.entities_emitted !== entities ||
        run.footer.counts.lineage_heads_emitted !== run.lineage_heads.length ||
        run.footer.counts.diagnostics !== diagnostics) {
        context.addIssue({ code: 'custom', path: ['footer', 'counts'], message: 'Atoms footer counts do not match.' });
    }
    const complete = Object.values(run.footer.coverage).every(value => value === 'complete');
    if (run.footer.complete !== complete) {
        context.addIssue({
            code: 'custom',
            path: ['footer', 'complete'],
            message: 'Atoms completeness is inconsistent.',
        });
    }
});
/** Consume through EOF and cross-check the V2 atoms stream emitted by this build. */
export async function collectAtomsRun(events) {
    let header;
    let footer;
    let seenHead = false;
    const diagnostics = [];
    const episodes = [];
    const lineageHeads = [];
    for await (const raw of events) {
        const event = OccAtomsEventV2Schema.parse(raw);
        if (footer)
            throw new Error('Atoms event encountered after the footer.');
        if (!header) {
            if (event.type !== 'header')
                throw new Error('Atoms stream must begin with exactly one header.');
            header = event;
            continue;
        }
        if (event.type === 'header')
            throw new Error('Atoms stream contains a duplicate header.');
        if (event.type === 'footer') {
            footer = event;
            continue;
        }
        if (event.type === 'diagnostic') {
            diagnostics.push(event);
            continue;
        }
        if (event.type === 'lineage-head') {
            seenHead = true;
            lineageHeads.push(event);
            continue;
        }
        if (seenHead)
            throw new Error('Atoms episode encountered after lineage-head records.');
        episodes.push(event);
    }
    if (!header)
        throw new Error('Atoms stream is missing its header.');
    if (!footer)
        throw new Error('Atoms stream is missing its footer.');
    return OccAtomsRunV2Schema.parse({ header, diagnostics, episodes, lineage_heads: lineageHeads, footer });
}
/** Stream deterministic, source-body-free work episodes over a bounded full Git DAG. */
export async function* emitAtoms(input) {
    yield* emitAtomsWithPolicy(input, true);
}
/** @internal Reuse the lineage overlay without computing patch-only episode fields. */
export async function* observeAtomsForEntityGraph(input) {
    yield* emitAtomsWithPolicy(input, false);
}
//# sourceMappingURL=atoms.js.map