"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = __importDefault(require("node:test"));
const strict_1 = __importDefault(require("node:assert/strict"));
const index_js_1 = require("../index.js");
// writeNodeWithEdges now rejects writes whose accountId does not
// match the spawning process's ACCOUNT_ID. These provenance-gate tests
// predate that gate; they pass a literal `expectedAccountId` so they
// exercise only the provenance gate they were written to test.
const TEST_ACCOUNT_ID = "12345678-9abc-def0-1234-56789abcdef0";
function makeStubSession(opts) {
    const calls = [];
    const tx = {
        async run(cypher, params = {}) {
            calls.push({ cypher, params });
            if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
                const ids = params.ids ?? [];
                const records = ids
                    .filter((id) => opts.preCheckLabelsByTarget.has(id))
                    .map((id) => ({
                    get(field) {
                        if (field === "id")
                            return id;
                        if (field === "labels")
                            return opts.preCheckLabelsByTarget.get(id) ?? [];
                        throw new Error(`unknown field ${field}`);
                    },
                }));
                return { records };
            }
            if (cypher.includes("CREATE (n:")) {
                return {
                    records: [
                        {
                            get(field) {
                                if (field === "nodeId")
                                    return "new-element-id";
                                if (field === "nodeLabels")
                                    return ["Person"];
                                throw new Error(`unknown field ${field}`);
                            },
                        },
                    ],
                };
            }
            if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) {
                return {
                    records: [],
                    summary: {
                        counters: {
                            updates() {
                                return { relationshipsCreated: opts.edgesCreatedPerCall ?? 1 };
                            },
                        },
                    },
                };
            }
            return { records: [] };
        },
    };
    const session = {
        async executeWrite(work) {
            return await work(tx);
        },
    };
    return { session, calls };
}
(0, node_test_1.default)("ACTION_PROVENANCE_LABELS includes the documented set", () => {
    for (const label of [
        "Person",
        "UserProfile",
        "AdminUser",
        "Organization",
        "LocalBusiness",
        "CloudflareTunnel",
        "CloudflareHostname",
    ]) {
        strict_1.default.ok(index_js_1.ACTION_PROVENANCE_LABELS.has(label), `${label} should be in ACTION_PROVENANCE_LABELS`);
    }
});
// Task 580 relaxed missing-provenance from a hard reject to a soft warn.
// The write now proceeds; a single structured stderr line carries the labels
// and agent so operators can quantify how often the gap fires.
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write missing any inbound PRODUCED edge and emits a warn line", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([["other-target", ["LocalBusiness"]]]),
    });
    const relationships = [
        // No inbound PRODUCED at all — neither Task nor Conversation nor Message.
        { type: "PART_OF", direction: "outgoing", targetNodeId: "other-target" },
    ];
    const captured = [];
    const originalWrite = process.stderr.write.bind(process.stderr);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    process.stderr.write = ((chunk) => {
        captured.push(typeof chunk === "string" ? chunk : String(chunk));
        return true;
    });
    try {
        const res = await (0, index_js_1.writeNodeWithEdges)({
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            session: session,
            labels: ["Person"],
            props: { accountId: TEST_ACCOUNT_ID },
            relationships,
            createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
            expectedAccountId: TEST_ACCOUNT_ID,
        });
        strict_1.default.equal(res.edgesCreated, 1);
    }
    finally {
        process.stderr.write = originalWrite;
    }
    const warnLine = captured.find((l) => l.includes("[graph-write] warn reason=missing-provenance"));
    strict_1.default.ok(warnLine, `expected warn line; captured=${JSON.stringify(captured)}`);
    strict_1.default.match(warnLine, /labels=Person/);
    strict_1.default.match(warnLine, /agent=maxy-admin/);
});
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write with PRODUCED-from-Task edge", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([
            ["task-id", ["Task"]],
        ]),
    });
    const relationships = [
        { type: "PRODUCED", direction: "incoming", targetNodeId: "task-id" },
    ];
    const res = await (0, index_js_1.writeNodeWithEdges)({
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        session: session,
        labels: ["Person"],
        props: { accountId: TEST_ACCOUNT_ID },
        relationships,
        createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
        expectedAccountId: TEST_ACCOUNT_ID,
    });
    strict_1.default.equal(res.edgesCreated, 1);
});
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write with no Task edge when agent='system' (bootstrap exemption)", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([["admin-user-id", ["AdminUser"]]]),
    });
    const relationships = [
        { type: "OWNS", direction: "incoming", targetNodeId: "admin-user-id" },
    ];
    const res = await (0, index_js_1.writeNodeWithEdges)({
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        session: session,
        labels: ["Person"],
        props: { accountId: TEST_ACCOUNT_ID },
        relationships,
        createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
        expectedAccountId: TEST_ACCOUNT_ID,
    });
    strict_1.default.equal(res.edgesCreated, 1);
});
// gate widened to accept:Conversation provenance for direct
// admin asks. The wrapper auto-injects the inbound PRODUCED edge from the
// active :AdminConversation via SESSION_NODE_ID.
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write with PRODUCED-from-Conversation edge", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([
            // AdminConversation carries the parent :Conversation label too.
            ["conv-id", ["Conversation", "AdminConversation"]],
        ]),
    });
    const relationships = [
        { type: "PRODUCED", direction: "incoming", targetNodeId: "conv-id" },
    ];
    const res = await (0, index_js_1.writeNodeWithEdges)({
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        session: session,
        labels: ["Person"],
        props: { accountId: TEST_ACCOUNT_ID },
        relationships,
        createdBy: { agent: "maxy-admin", session: "sess-1", tool: "contact-create" },
        expectedAccountId: TEST_ACCOUNT_ID,
    });
    strict_1.default.equal(res.edgesCreated, 1);
});
// Message subtypes (UserMessage, AssistantMessage, AdminMessage)
// also qualify because the labels() array includes the parent :Message label.
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write with PRODUCED-from-Message edge", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([
            ["msg-id", ["Message", "UserMessage"]],
        ]),
    });
    const relationships = [
        { type: "PRODUCED", direction: "incoming", targetNodeId: "msg-id" },
    ];
    const res = await (0, index_js_1.writeNodeWithEdges)({
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        session: session,
        labels: ["Person"],
        props: { accountId: TEST_ACCOUNT_ID },
        relationships,
        createdBy: { agent: "maxy-admin", session: "sess-1", tool: "contact-create" },
        expectedAccountId: TEST_ACCOUNT_ID,
    });
    strict_1.default.equal(res.edgesCreated, 1);
});
// Defence-in-depth: a PRODUCED edge whose source is not in
// {Task, Conversation, Message} does not satisfy the gate. Under the
// Task 580 soft semantics the write still proceeds and the warn line fires —
// the source-label allowlist now governs whether the write counts as
// provenance-satisfied for the warn check, not whether it lands.
(0, node_test_1.default)("writeNodeWithEdges accepts :Person write with PRODUCED edge from a non-provenance-source label and emits a warn line", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([
            ["business-id", ["LocalBusiness"]],
        ]),
    });
    const relationships = [
        { type: "PRODUCED", direction: "incoming", targetNodeId: "business-id" },
    ];
    const captured = [];
    const originalWrite = process.stderr.write.bind(process.stderr);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    process.stderr.write = ((chunk) => {
        captured.push(typeof chunk === "string" ? chunk : String(chunk));
        return true;
    });
    try {
        const res = await (0, index_js_1.writeNodeWithEdges)({
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            session: session,
            labels: ["Person"],
            props: { accountId: TEST_ACCOUNT_ID },
            relationships,
            createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
            expectedAccountId: TEST_ACCOUNT_ID,
        });
        strict_1.default.equal(res.edgesCreated, 1);
    }
    finally {
        process.stderr.write = originalWrite;
    }
    const warnLine = captured.find((l) => l.includes("[graph-write] warn reason=missing-provenance"));
    strict_1.default.ok(warnLine, `expected warn line; captured=${JSON.stringify(captured)}`);
});
(0, node_test_1.default)("writeNodeWithEdges accepts non-action-provenance label writes without Task edge", async () => {
    const { session } = makeStubSession({
        preCheckLabelsByTarget: new Map([["doc-id", ["KnowledgeDocument"]]]),
    });
    const relationships = [
        { type: "ABOUT", direction: "outgoing", targetNodeId: "doc-id" },
    ];
    const res = await (0, index_js_1.writeNodeWithEdges)({
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        session: session,
        labels: ["Question"], // not in ACTION_PROVENANCE_LABELS
        props: { accountId: TEST_ACCOUNT_ID },
        relationships,
        createdBy: { agent: "maxy-admin", session: "sess-1", tool: "memory-write" },
        expectedAccountId: TEST_ACCOUNT_ID,
    });
    strict_1.default.equal(res.edgesCreated, 1);
});
//# sourceMappingURL=action-provenance-gate.test.js.map