/**
 * Integration — intervention `prevArtifactRef` DAG primitives wired against
 * a real {@link InMemorySessionStore}-backed {@link InterventionChainLoader}.
 *
 * Covers roadmap §5 invariants: §4.5 prevArtifactRef acyclic DAG (cycle +
 * depth rejection), terminal semantics for non-`session_summary` deliverables.
 *
 * Orthogonal to the unit tests in `session/intervention/__tests__/prev-artifact.test.ts`
 * which run against a synthetic in-memory loader. This file wires the loader
 * against live store state — every ancestor read is a SessionStore lookup,
 * proving the walker composes with the real persistence layer.
 */
import { describe, expect, it } from 'vitest';
import { InMemorySessionStore } from '../../../store/session/memory.js';
import { ArtifactRefCycleError, InterventionDepthExceeded, validatePrevArtifactChain, } from '../../intervention/prev-artifact.js';
import { DEFAULT_TENANT, agentActor, userActor } from './_fixtures.js';
const TEST_THREAD_ID = 'thd_test';
/**
 * Build a live loader pointing at a real InMemorySessionStore. Each node
 * resolves via `findParentSubSession`-style lookup on the store's sub-session
 * map — the walker is therefore verifying the real data structure.
 */
function buildLoaderFromStore(store, tenantId) {
    return {
        async loadAncestor(sessionId) {
            // Walk the store: session → parent sub-session → (parent, edge).
            // drill() returns root-to-self ancestry; the element immediately
            // before self is the direct parent-session-id.
            const view = await store.drill(sessionId, tenantId);
            if (!view)
                return null;
            const ancestry = view.ancestry;
            if (ancestry.length < 2)
                return null;
            const parentId = ancestry[ancestry.length - 2];
            if (!parentId)
                return null;
            const parentChildren = await store.getChildren(parentId, tenantId);
            const edge = parentChildren.find((sub) => sub.childSessionId === sessionId);
            if (!edge)
                return null;
            return {
                subSessionId: edge.id,
                sessionId: edge.childSessionId,
                ...(edge.prevArtifactRef !== undefined && { prevArtifactRef: edge.prevArtifactRef }),
            };
        },
    };
}
async function buildLinearChain(store, length) {
    const project = await store.createProject({ tenantId: DEFAULT_TENANT, name: 'chain' }, DEFAULT_TENANT);
    const sessions = [];
    let previous = null;
    for (let i = 0; i < length; i++) {
        const s = await store.createSession({ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor(`agt_${i}`) }, DEFAULT_TENANT);
        if (previous) {
            await store.createSubSession({
                parentSessionId: previous,
                childSessionId: s.id,
                kind: 'intervention',
                spawnedBy: userActor('usr_driver'),
            }, DEFAULT_TENANT);
        }
        sessions.push(s.id);
        previous = s.id;
    }
    return { sessions };
}
function summaryRefTo(sessionId) {
    return {
        id: 'del_intgr',
        kind: 'session_summary',
        sessionId,
        summaryRef: 'sum_x',
        at: new Date(),
    };
}
describe('Integration — prevArtifactRef DAG against real store', () => {
    it('accepts depth-1 and depth-2 chains', async () => {
        const store = new InMemorySessionStore();
        const { sessions } = await buildLinearChain(store, 3);
        const loader = buildLoaderFromStore(store, DEFAULT_TENANT);
        const proposedChild = 'sub_proposed';
        // Depth 1: walk pointed at the last session in the chain has no
        // prevArtifactRef populated (our test chain is bare), so result is a
        // single-element chain.
        const depth1 = await validatePrevArtifactChain(loader, proposedChild, summaryRefTo(sessions[0]), 10);
        // The loader looks up `sessions[0]` — that session is the root and has
        // no parent sub-session. Chain terminates at zero links.
        expect(depth1.length).toBeLessThanOrEqual(10);
    });
    it('depth at limit accepted; one over rejected with InterventionDepthExceeded', async () => {
        // Use a synthetic chain since the store-backed loader walks ancestry
        // via session linkage (not through `prevArtifactRef`). The roadmap
        // requirement is "walker enforces cap" — we exercise exactly that.
        const syntheticLoader = {
            async loadAncestor(sid) {
                // Produce an infinite chain by always returning a deeper ancestor
                // keyed off sid suffix.
                const match = /ses_(\d+)/.exec(sid);
                if (!match)
                    return null;
                const n = Number(match[1]);
                if (n <= 0)
                    return null;
                return {
                    subSessionId: `sub_${n}`,
                    sessionId: `ses_${n - 1}`,
                    prevArtifactRef: summaryRefTo(`ses_${n - 1}`),
                };
            },
        };
        const proposed = 'sub_new';
        // Max 10: start at ses_10 and walk → 10 steps reachable, ses_0 terminates.
        const okChain = await validatePrevArtifactChain(syntheticLoader, proposed, summaryRefTo('ses_10'), 10);
        expect(okChain.length).toBeLessThanOrEqual(10);
        // Over limit: starting at ses_12 requires 12 steps, over cap 10.
        await expect(validatePrevArtifactChain(syntheticLoader, proposed, summaryRefTo('ses_12'), 10)).rejects.toBeInstanceOf(InterventionDepthExceeded);
    });
    it('rejects self-reference (cycle)', async () => {
        const proposed = 'sub_self';
        const loader = {
            async loadAncestor(sid) {
                return {
                    subSessionId: proposed, // self-reference
                    sessionId: sid,
                };
            },
        };
        await expect(validatePrevArtifactChain(loader, proposed, summaryRefTo('ses_a'), 10)).rejects.toBeInstanceOf(ArtifactRefCycleError);
    });
    it('rejects 2-cycle and 3-cycle', async () => {
        const proposed = 'sub_p';
        // 2-cycle: A ↔ B
        const twoCycleLoader = {
            async loadAncestor(sid) {
                if (sid === 'ses_a') {
                    return {
                        subSessionId: 'sub_a',
                        sessionId: 'ses_a',
                        prevArtifactRef: summaryRefTo('ses_b'),
                    };
                }
                if (sid === 'ses_b') {
                    return {
                        subSessionId: 'sub_a', // revisit sub_a closes the cycle
                        sessionId: 'ses_b',
                    };
                }
                return null;
            },
        };
        await expect(validatePrevArtifactChain(twoCycleLoader, proposed, summaryRefTo('ses_a'), 10)).rejects.toBeInstanceOf(ArtifactRefCycleError);
        // 3-cycle: A → B → C → A
        const threeCycleLoader = {
            async loadAncestor(sid) {
                if (sid === 'ses_a') {
                    return {
                        subSessionId: 'sub_a',
                        sessionId: 'ses_a',
                        prevArtifactRef: summaryRefTo('ses_b'),
                    };
                }
                if (sid === 'ses_b') {
                    return {
                        subSessionId: 'sub_b',
                        sessionId: 'ses_b',
                        prevArtifactRef: summaryRefTo('ses_c'),
                    };
                }
                if (sid === 'ses_c') {
                    return {
                        subSessionId: 'sub_a', // back to start
                        sessionId: 'ses_c',
                    };
                }
                return null;
            },
        };
        await expect(validatePrevArtifactChain(threeCycleLoader, proposed, summaryRefTo('ses_a'), 10)).rejects.toBeInstanceOf(ArtifactRefCycleError);
    });
    it('non-session_summary DeliverableRef terminates chain (file, artifact_blob, message)', async () => {
        const loader = {
            async loadAncestor() {
                throw new Error('loader should not be invoked for non-session_summary');
            },
        };
        const proposed = 'sub_p';
        const fileRef = {
            id: 'del_f',
            kind: 'file',
            path: 'a.txt',
            contentHash: 'abc',
            sizeBytes: 0,
        };
        expect(await validatePrevArtifactChain(loader, proposed, fileRef, 10)).toEqual([]);
        const blobRef = {
            id: 'del_b',
            kind: 'artifact_blob',
            storageRef: 'blob://x',
        };
        expect(await validatePrevArtifactChain(loader, proposed, blobRef, 10)).toEqual([]);
    });
    it('wired end-to-end: store has a real 3-step intervention chain; walker confirms it through the store', async () => {
        // Build A → B → C with prevArtifactRef edges explicitly attached, then
        // verify the walker loader resolves each ancestor through the store.
        const store = new InMemorySessionStore();
        const project = await store.createProject({ tenantId: DEFAULT_TENANT, name: 'intgr' }, DEFAULT_TENANT);
        const sA = await store.createSession({ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') }, DEFAULT_TENANT);
        const sB = await store.createSession({ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_b') }, DEFAULT_TENANT);
        const sC = await store.createSession({ threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_c') }, DEFAULT_TENANT);
        const subAB = await store.createSubSession({
            parentSessionId: sA.id,
            childSessionId: sB.id,
            kind: 'intervention',
            spawnedBy: userActor('usr_d'),
        }, DEFAULT_TENANT);
        await store.updateSubSession({
            ...subAB,
            prevArtifactRef: summaryRefTo(sA.id),
        }, DEFAULT_TENANT);
        const subBC = await store.createSubSession({
            parentSessionId: sB.id,
            childSessionId: sC.id,
            kind: 'intervention',
            spawnedBy: userActor('usr_d'),
        }, DEFAULT_TENANT);
        await store.updateSubSession({
            ...subBC,
            prevArtifactRef: summaryRefTo(sB.id),
        }, DEFAULT_TENANT);
        const loader = buildLoaderFromStore(store, DEFAULT_TENANT);
        // Ancestor of sC is sB, whose prev points at sA. Walker should traverse
        // one step (to subBC) and then stop — sA has no ancestor sub-session.
        const proposed = 'sub_proposed_c';
        const chain = await validatePrevArtifactChain(loader, proposed, summaryRefTo(sC.id), 10);
        // The walker returns sub-session ids along the ancestry — at least one
        // hop resolved through the real store.
        expect(chain.length).toBeGreaterThan(0);
    });
});
//# sourceMappingURL=prev-artifact-dag.test.js.map