import { Compile } from "typebox/compile"; import { afterEach, describe, expect, it } from "vitest"; import { AuditRunInspectResultSchema, type DecisionReceiptV1, type ExecutionIdentityContextV1, } from "../../packages/gateway-protocol/src/index.js"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; import { activityRunInspectorSearch } from "../../ui/src/pages/activity/run-inspector-model.js"; import { tableExists } from "../state/openclaw-state-db-schema-helpers.js"; import { closeOpenClawStateDatabaseForTest, openOpenClawStateDatabase, } from "../state/openclaw-state-db.js"; import { recordAuditEvent } from "./audit-event-store.js"; import { pageExecutionDecisionFactsForContext, pruneExpiredExecutionDecisionFacts, recordExecutionDecisionFact, summarizeExecutionDecisionFactsForContext, } from "./execution-decision-facts.js"; import { presentExecutionDecisionReceipts } from "./execution-decision-receipts.js"; import { configureExecutionIdentityAdmissionSink, createExecutionIdentityAdmissionToken, enqueueExecutionIdentityContextAtAdmission, type ExecutionIdentityAdmissionEnvelope, } from "./execution-identity-admission.js"; import { processExecutionIdentityAdmissionWork } from "./execution-identity-context.js"; import { bindExecutionOwnerLifecycleMetadata } from "./execution-owner-lifecycle-binding-store.js"; import { configureMessageActionDecisionSink, recordMessageActionDecision, } from "./message-action-decision.js"; import { recordOutboundMessageProgress } from "./message-delivery-progress-store.js"; const RETENTION_MS = 30 * 24 * 60 * 60_000; afterEach(() => { closeOpenClawStateDatabaseForTest(); }); const tempDirs = useAutoCleanupTempDirTracker(afterEach); function databaseOptions() { return { env: { OPENCLAW_STATE_DIR: tempDirs.make("openclaw-decision-facts-") } }; } function seedExecutionContext( database: ReturnType, overrides: { runId?: string; contextId?: string; executionId?: string; } = {}, ): ExecutionIdentityContextV1 { const runId = overrides.runId ?? "run-1"; const contextId = overrides.contextId ?? "context-1"; const executionId = overrides.executionId ?? "execution-1"; let envelope: ExecutionIdentityAdmissionEnvelope | undefined; const clear = configureExecutionIdentityAdmissionSink((work) => { if (work.kind === "capture") { envelope = work.envelope; } return true; }); try { enqueueExecutionIdentityContextAtAdmission( { runId, agentId: "main", ingress: { kind: "local-cli", boundary: "agent-command.local", state: "present" }, runtime: { kind: "embedded" }, }, { enabled: true, now: 50, contextId, executionId, runtimeInstanceId: "runtime-1", }, ); } finally { clear(); } if (!envelope) { throw new Error("expected execution identity envelope"); } const stored = processExecutionIdentityAdmissionWork( { kind: "capture", envelope }, { ...database, now: 50 }, ); if ( stored.contextId !== contextId || stored.executionId !== executionId || stored.runId !== runId ) { throw new Error(`unexpected execution context: ${JSON.stringify(stored)}`); } return stored; } function receipt(id: string, occurredAt = 100): DecisionReceiptV1 { return { schemaVersion: 1, receiptId: id, contextId: "context-1", executionId: "execution-1", runId: "run-1", actionId: `action-${id}`, occurredAt, action: { family: "tool", operation: "policy" }, decision: { outcome: "denied", reasonCode: "tool_policy_denied" }, enforcement: { coverageState: "enforced", evaluatorRef: "tool-policy", policyRefs: ["tool-policy:deny"], grantRefs: [], contextFieldsUsed: ["runId"], }, source: { owner: "tool-policy", recordRef: `record-${id}`, decisionBoundary: "agent-tool.before-call", }, missingEvidence: [], remediation: [{ code: "choose_allowed_tool", text: "Choose an allowed tool and retry." }], }; } function tokenForContext(context: ExecutionIdentityContextV1) { return createExecutionIdentityAdmissionToken(context.runId, { contextId: context.contextId, executionId: context.executionId, now: context.createdAt, }); } describe("execution decision facts", () => { it("persists repeated same-reason broadcast denials with opaque distinct ids", () => { const database = databaseOptions(); seedExecutionContext(database); const token = createExecutionIdentityAdmissionToken("run-1", { contextId: "context-1", executionId: "execution-1", now: 100, }); const clear = configureMessageActionDecisionSink( (decision) => recordExecutionDecisionFact(decision, { ...database, now: 100 }) === "inserted", ); try { for (const receiptDiscriminator of ["broadcast:0", "broadcast:1"]) { expect( recordMessageActionDecision({ token, actionId: "broadcast-action", action: "broadcast", channel: "qa-channel", outcome: "denied", reasonCode: "message_target_unknown", coverageState: "enforced", policyRefs: ["message-target:known"], summary: "Message action was denied before platform delivery.", remediation: [], receiptDiscriminator, occurredAt: 100, }), ).toBe(true); } } finally { clear(); } const receipts = pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 10, now: 100, database, }).receipts; expect(receipts).toHaveLength(2); expect(new Set(receipts.map((item) => item.receiptId)).size).toBe(2); expect(JSON.stringify(receipts)).not.toContain("broadcast:0"); expect(JSON.stringify(receipts)).not.toContain("broadcast:1"); }); it("projects owner-native outbound delivery into run inspection", () => { const database = databaseOptions(); const context = seedExecutionContext(database); const now = Date.now(); const storedEvent = recordAuditEvent( { sourceId: "message:outbound:queue:delivery-1:payload:0", sourceSequence: 1, occurredAt: now, kind: "message", action: "message.outbound.finished", status: "succeeded", outcome: "sent", actorType: "agent", actorId: "main", agentId: "main", runId: "run-1", executionIdentityToken: tokenForContext(context), direction: "outbound", channel: "qa-channel", conversationKind: "direct", resultCount: 1, targetId: "raw-target", messageId: "raw-message-id", }, database, ); if (!storedEvent) { throw new Error("expected owner-native message event"); } const inspection = presentExecutionDecisionReceipts({ context, decisionLimit: 10, options: { ...database, now }, }); expect(inspection.decisions).toEqual( expect.arrayContaining([ expect.objectContaining({ action: expect.objectContaining({ family: "message", operation: "send" }), decision: { outcome: "allowed", reasonCode: "message_delivered" }, enforcement: expect.objectContaining({ coverageState: "attribution-only" }), source: expect.objectContaining({ owner: "audit_events" }), }), ]), ); expect(inspection.decisionDisplays).toEqual( expect.arrayContaining([ expect.objectContaining({ action: expect.objectContaining({ family: "message", operation: "send" }), decision: { outcome: "allowed", reasonCode: "message_delivered" }, provenance: { state: "verified", producer: "message-delivery" }, }), ]), ); const messageReceipt = inspection.decisions.find((item) => item.action.family === "message"); const messageDisplay = inspection.decisionDisplays.find( (item) => item.provenance.state === "verified" && item.provenance.producer === "message-delivery", ); expect(messageDisplay?.selectorId).toBe( `message-decision:${2_000_000_000_000 + storedEvent.sequence}`, ); expect(messageDisplay?.selectorId).not.toBe(messageReceipt?.receiptId); const receiptSearch = activityRunInspectorSearch( { kind: "run", id: context.runId }, { id: messageDisplay?.selectorId ?? "" }, ); expect(new URLSearchParams(receiptSearch.slice(1)).get("receipt")).toBe( messageDisplay?.selectorId, ); expect(receiptSearch).not.toContain(encodeURIComponent(messageReceipt?.receiptId ?? "")); }); it("projects exact-bound cron, task, and flow owner rows without generic facts", () => { const database = databaseOptions(); const context = seedExecutionContext(database); const db = openOpenClawStateDatabase(database).db; db.prepare( `INSERT INTO cron_run_receipts ( receipt_id, store_key, job_id, config_revision, agent_id, request_run_id, status, owner_pid, started_at_ms, finished_at_ms ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( "cron-receipt-1", "default", "job-1", "revision-1", "main", context.runId, "ok", 1, 60, 70, ); db.prepare( `INSERT INTO task_runs ( task_id, runtime, owner_key, scope_kind, task, status, delivery_status, notify_policy, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( "task-1", "cron", "owner-1", "session", "private task text", "succeeded", "not-requested", "never", 61, ); db.prepare( `INSERT INTO flow_runs ( flow_id, owner_key, status, notify_policy, goal, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?)`, ).run("flow-1", "owner-1", "succeeded", "never", "private flow goal", 62, 70); for (const [ownerKind, ownerId] of [ ["cron", "cron-receipt-1"], ["task", "task-1"], ["flow", "flow-1"], ] as const) { expect( bindExecutionOwnerLifecycleMetadata({ db, ownerKind, ownerId, binding: { contextId: context.contextId, executionId: context.executionId }, }), ).toBe("bound"); } const result = presentExecutionDecisionReceipts({ context, decisionLimit: 10, options: { ...database, now: 100 }, }); expect(result.decisions.map((item) => item.source.owner)).toEqual([ "agent-command", "cron_run_receipts", "task_runs", "flow_runs", ]); expect(JSON.stringify(result.decisions)).not.toContain("private task text"); expect(JSON.stringify(result.decisions)).not.toContain("private flow goal"); expect(tableExists(db, "execution_decision_facts")).toBe(false); }); it("does not assign run-only delivery evidence to either exact execution sharing a run id", () => { const database = databaseOptions(); const first = seedExecutionContext(database, { runId: "shared-run", contextId: "context-first", executionId: "execution-first", }); const second = seedExecutionContext(database, { runId: "shared-run", contextId: "context-second", executionId: "execution-second", }); const now = Date.now(); recordAuditEvent( { sourceId: "message:shared-run:unbound", sourceSequence: 1, occurredAt: now, kind: "message", action: "message.outbound.finished", status: "succeeded", outcome: "sent", actorType: "agent", actorId: "main", agentId: "main", runId: "shared-run", direction: "outbound", channel: "qa-channel", conversationKind: "direct", resultCount: 1, }, database, ); for (const context of [first, second]) { expect( presentExecutionDecisionReceipts({ context, decisionLimit: 10, options: { ...database, now }, }).decisions.filter((item) => item.action.family === "message"), ).toEqual([]); } expect( tableExists(openOpenClawStateDatabase(database).db, "outbound_message_execution_bindings"), ).toBe(false); recordAuditEvent( { sourceId: "message:shared-run:first-execution", sourceSequence: 2, occurredAt: now + 1, kind: "message", action: "message.outbound.finished", status: "succeeded", outcome: "sent", actorType: "agent", actorId: "main", agentId: "main", runId: "shared-run", executionIdentityToken: tokenForContext(first), direction: "outbound", channel: "qa-channel", conversationKind: "direct", resultCount: 1, }, database, ); const messageReceipts = (context: ExecutionIdentityContextV1) => presentExecutionDecisionReceipts({ context, decisionLimit: 10, options: { ...database, now: now + 1 }, }).decisions.filter((item) => item.action.family === "message"); expect(messageReceipts(first)).toHaveLength(1); expect(messageReceipts(second)).toEqual([]); expect( openOpenClawStateDatabase(database) .db.prepare( "SELECT context_id, execution_id, run_id FROM outbound_message_execution_bindings", ) .all(), ).toEqual([ { context_id: "context-first", execution_id: "execution-first", run_id: "shared-run", }, ]); }); it("keeps delivery stages distinct, redacted, replay-safe, and retention bounded", () => { const database = databaseOptions(); const context = seedExecutionContext(database); const now = Date.now(); const common = { sourceSequence: 1, occurredAt: now, kind: "message" as const, actorType: "agent" as const, actorId: "main", agentId: "main", runId: "run-1", executionIdentityToken: tokenForContext(context), direction: "outbound" as const, channel: "qa-channel", conversationKind: "direct" as const, targetId: "raw-channel-target", }; const events = [ { ...common, occurredAt: now, sourceId: "queue-1:queued", action: "message.outbound.queued" as const, status: "started" as const, outcome: "queued" as const, }, { ...common, occurredAt: now + 1, sourceId: "queue-1:platform", action: "message.outbound.platform-started" as const, status: "started" as const, outcome: "platform_started" as const, }, { ...common, occurredAt: now + 2, sourceId: "queue-1:finished", action: "message.outbound.finished" as const, status: "succeeded" as const, outcome: "sent" as const, messageId: "raw-platform-message", }, { ...common, occurredAt: now + 3, sourceId: "queue-2:failed", action: "message.outbound.finished" as const, status: "failed" as const, outcome: "failed" as const, errorCode: "message_delivery_failed" as const, failureStage: "queue" as const, }, { ...common, occurredAt: now + 4, sourceId: "queue-3:failed", action: "message.outbound.finished" as const, status: "failed" as const, outcome: "failed" as const, errorCode: "message_delivery_failed" as const, failureStage: "platform_send" as const, }, { ...common, occurredAt: now + 5, sourceId: "queue-4:suppressed", action: "message.outbound.finished" as const, status: "blocked" as const, outcome: "suppressed" as const, reasonCode: "no_visible_payload" as const, }, ]; for (const event of events) { expect( event.action === "message.outbound.finished" ? recordAuditEvent(event, database) : recordOutboundMessageProgress(event, database), ).toBeDefined(); } expect( recordOutboundMessageProgress( { ...common, occurredAt: now, sourceId: "queue-1:queued", action: "message.outbound.queued", status: "started", outcome: "queued", }, database, ), ).toBeUndefined(); const inspect = () => presentExecutionDecisionReceipts({ context, decisionCursor: "m:0:0", decisionLimit: 10, options: { ...database, now }, }); const firstInspection = inspect(); expect(firstInspection.decisions.map((item) => item.decision.reasonCode)).toEqual([ "message_queued", "message_platform_started", "message_delivered", "message_delivery_failed_queue", "message_delivery_failed_platform_send", "message_suppressed_no_visible_payload", ]); expect(firstInspection.decisions.map((item) => item.enforcement.coverageState)).toEqual( Array.from({ length: 6 }, () => "attribution-only"), ); expect(firstInspection.decisions.map((item) => item.source.owner)).toEqual([ "outbound_message_progress", "outbound_message_progress", "audit_events", "audit_events", "audit_events", "audit_events", ]); const selectors = firstInspection.decisionDisplays.map((item) => item.selectorId); expect(selectors).toHaveLength(firstInspection.decisions.length); expect(new Set(selectors).size).toBe(selectors.length); expect(selectors.every((selector) => selector.startsWith("message-decision:"))).toBe(true); expect(JSON.stringify(firstInspection)).not.toContain("raw-channel-target"); expect(JSON.stringify(firstInspection)).not.toContain("raw-platform-message"); closeOpenClawStateDatabaseForTest(); expect(inspect().decisionDisplays.map((item) => item.selectorId)).toEqual(selectors); expect( presentExecutionDecisionReceipts({ context, decisionCursor: "m:0:0", decisionLimit: 10, options: { ...database, now: now + RETENTION_MS + events.length + 1 }, }).decisions, ).toEqual([]); }); it("stays absent until a future owner writes one immutable fact", () => { const database = databaseOptions(); seedExecutionContext(database); const opened = openOpenClawStateDatabase(database); expect(tableExists(opened.db, "execution_decision_facts")).toBe(false); expect(pruneExpiredExecutionDecisionFacts({ database })).toBe(0); expect(tableExists(opened.db, "execution_decision_facts")).toBe(false); expect(recordExecutionDecisionFact(receipt("receipt-1"), { ...database, now: 100 })).toBe( "inserted", ); expect(recordExecutionDecisionFact(receipt("receipt-1"), { ...database, now: 100 })).toBe( "existing", ); expect(() => recordExecutionDecisionFact( { ...receipt("receipt-1"), decision: { outcome: "allowed", reasonCode: "changed" } }, { ...database, now: 100 }, ), ).toThrow("conflicts with retained state"); expect( pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 10, now: 100, database, }).receipts, ).toEqual([receipt("receipt-1")]); expect( summarizeExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, now: 100, database, }), ).toEqual({ count: 1, coverageState: "enforced", missingEvidence: [] }); }); it("rejects approval duplication before creating the generic table", () => { const database = databaseOptions(); expect(() => recordExecutionDecisionFact( { ...receipt("approval-duplicate"), source: { owner: "operator_approvals", recordRef: "approval-ref", decisionBoundary: "gateway.operator-approval.first-answer", }, }, { ...database, now: 100 }, ), ).toThrow("owner-native table"); expect(tableExists(openOpenClawStateDatabase(database).db, "execution_decision_facts")).toBe( false, ); }); it("keeps high-cardinality summary work bounded and conservative", () => { const database = databaseOptions(); seedExecutionContext(database); for (let index = 0; index < 130; index += 1) { recordExecutionDecisionFact(receipt(`bounded-${String(index).padStart(3, "0")}`), { ...database, now: 100, limits: { maxRows: 1_000, pruneBatchRows: 10 }, }); } expect( summarizeExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, now: 100, database, }), ).toEqual({ count: 129, coverageState: "unknown", missingEvidence: ["decision.fact.summary_bounded"], }); }); it("pages equal-time facts by a bounded row key", () => { const database = databaseOptions(); const context = seedExecutionContext(database); for (const id of ["same-time-a", "same-time-b", "same-time-c"]) { recordExecutionDecisionFact(receipt(id, 100), { ...database, now: 100 }); } const first = pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 1, now: 100, database, }); expect(first.receipts.map((item) => item.receiptId)).toEqual(["same-time-a"]); expect(first.entries).toEqual([ { receipt: receipt("same-time-a", 100), selectorId: "decision-fact:1" }, ]); expect(first.nextCursor).toEqual({ occurredAt: 100, rowId: expect.any(Number) }); const second = pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, after: first.nextCursor, limit: 2, now: 100, database, }); expect(second.receipts.map((item) => item.receiptId)).toEqual(["same-time-b", "same-time-c"]); expect(second.entries.map((entry) => entry.selectorId)).toEqual([ "decision-fact:2", "decision-fact:3", ]); expect( pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 1, now: 100, database, }).entries, ).toEqual(first.entries); expect( new Set([...first.entries, ...second.entries].map((entry) => entry.selectorId)).size, ).toBe(3); for (const decisionCursor of ["1", "001"]) { const legacyPage = presentExecutionDecisionReceipts({ context, decisionCursor, decisionLimit: 1, options: { ...database, now: 100 }, }); expect(legacyPage.decisions.map((item) => item.receiptId)).toEqual(["same-time-a"]); expect(legacyPage.decisionDisplays?.map((item) => item.selectorId)).toEqual([ "decision-fact:1", ]); expect(legacyPage.nextDecisionCursor).toMatch(/^g:/); } const legacyPage = presentExecutionDecisionReceipts({ context, decisionCursor: "1", decisionLimit: 1, options: { ...database, now: 100 }, }); const next = presentExecutionDecisionReceipts({ context, decisionCursor: legacyPage.nextDecisionCursor, decisionLimit: 2, options: { ...database, now: 100 }, }); expect(next.decisions.map((item) => item.receiptId)).toEqual(["same-time-b", "same-time-c"]); expect(next.decisionDisplays?.map((item) => item.selectorId)).toEqual([ "decision-fact:2", "decision-fact:3", ]); }); it("replaces unverified aggregate evidence with a fixed display marker", () => { const database = databaseOptions(); seedExecutionContext(database); const context: ExecutionIdentityContextV1 = { schemaVersion: 1, contextId: "context-1", executionId: "execution-1", runId: "run-1", createdAt: 50, trustDomain: { kind: "gateway-cell", domainRef: "domain-1", state: "present" }, invoker: { state: "absent" }, ingress: { kind: "local-cli", boundary: "agent-command.local", state: "present" }, agentPrincipal: { kind: "agent", domainRef: "domain-1", principalRef: "agent-main" }, agentDefinition: { definitionRef: "main", state: "present" }, runtimeInstance: { runtimeRef: "runtime-1", kind: "embedded", state: "present" }, applicableGrants: [], assurance: [], coverageState: "unattributed", missingEvidence: [], }; for (const owner of ["one", "two"] as const) { recordExecutionDecisionFact( { ...receipt(owner), missingEvidence: Array.from( { length: 16 }, (_, index) => `${owner}.missing.${String(index).padStart(2, "0")}`, ), }, { ...database, now: 100 }, ); } const result = presentExecutionDecisionReceipts({ context, decisionLimit: 10, options: { ...database, now: 100 }, }); expect(result.coverage).toEqual({ state: "unknown", missingEvidence: expect.arrayContaining(["decision.display_provenance"]), }); expect(JSON.stringify(result.coverage)).not.toContain("one.missing"); expect(JSON.stringify(result.coverage)).not.toContain("two.missing"); expect(Compile(AuditRunInspectResultSchema).Check(result)).toBe(false); }); it("keeps receipt-controlled prose out of the owner display projection", () => { const database = databaseOptions(); const context = seedExecutionContext(database); const receiptIdSecret = "U2_R6_RECEIPT_ID_SECRET_42b17d"; const summarySecret = "U2_R6_SUMMARY_SECRET_7c4f9a"; const remediationCodeSecret = "U2_R6_CODE_SECRET_81d2be"; const remediationTextSecret = "U2_R6_TEXT_SECRET_36a5c0"; const policyRefSecret = "U2_R6_POLICY_REF_SECRET_ea731c"; const grantRefSecret = "U2_R6_GRANT_REF_SECRET_b529f4"; const missingEvidenceSecret = "U2_R6_MISSING_EVIDENCE_SECRET_2d97c1"; recordExecutionDecisionFact( { ...receipt(receiptIdSecret), action: { family: "tool", operation: "policy", summary: summarySecret, }, source: { owner: "audit_events", recordRef: "forged-core-looking-record", decisionBoundary: "message.outbound.finished", }, enforcement: { coverageState: "enforced", evaluatorRef: "forged-core-looking-evaluator", policyRefs: [policyRefSecret], grantRefs: [grantRefSecret], contextFieldsUsed: ["runId"], }, missingEvidence: [missingEvidenceSecret], remediation: [{ code: remediationCodeSecret, text: remediationTextSecret }], }, { ...database, now: 100 }, ); const result = presentExecutionDecisionReceipts({ context, decisionLimit: 10, options: { ...database, now: 100 }, }); expect(result.decisionDisplays).toBeDefined(); const displayJson = JSON.stringify(result.decisionDisplays); expect(displayJson).not.toContain(receiptIdSecret); expect(displayJson).not.toContain(summarySecret); expect(displayJson).not.toContain(remediationCodeSecret); expect(displayJson).not.toContain(remediationTextSecret); expect(displayJson).not.toContain(policyRefSecret); expect(displayJson).not.toContain(grantRefSecret); expect(JSON.stringify(result.coverage)).not.toContain(missingEvidenceSecret); expect(result.coverage).toEqual({ state: "unknown", missingEvidence: expect.arrayContaining(["decision.display_provenance"]), }); expect(JSON.stringify(result.decisions)).toContain(policyRefSecret); expect(JSON.stringify(result.decisions)).toContain(grantRefSecret); expect(result.decisionDisplays).toEqual([ expect.objectContaining({ action: expect.objectContaining({ summary: "Run admission was recorded without an identity-aware policy or grant decision.", }), provenance: { state: "verified", producer: "run-admission" }, }), expect.objectContaining({ selectorId: "decision-fact:1", action: { family: "decision", operation: "record" }, decision: { outcome: "unknown", reasonCode: "decision_fact_display_unverified" }, enforcement: { coverageState: "unknown", policyCount: 0, grantCount: 0, contextFieldsUsed: [], }, provenance: { state: "unverified" }, missingEvidence: ["decision.display_provenance"], remediation: [], }), ]); expect(Compile(AuditRunInspectResultSchema).Check(result)).toBe(false); }); it("rejects a generic fact whose context, execution, and run tuple is not exact", () => { const database = databaseOptions(); seedExecutionContext(database); expect(() => recordExecutionDecisionFact( { ...receipt("wrong-execution"), executionId: "execution-2" }, database, ), ).toThrow("exact retained execution context"); expect(tableExists(openOpenClawStateDatabase(database).db, "execution_decision_facts")).toBe( false, ); }); it("projects a fact as unknown when the requested tuple does not match", () => { const database = databaseOptions(); seedExecutionContext(database); recordExecutionDecisionFact(receipt("tuple-mismatch"), { ...database, now: 100 }); const page = pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-2", runId: "run-1" }, limit: 10, now: 100, database, }); expect(page.entries).toEqual([ { selectorId: "decision-fact:1", receipt: expect.objectContaining({ decision: { outcome: "unknown", reasonCode: "decision_fact_execution_link_mismatch" }, enforcement: expect.objectContaining({ coverageState: "unknown" }), missingEvidence: ["decision.execution_link"], }), }, ]); }); it("enforces the 30-day read boundary and bounded retention pruning", () => { const database = databaseOptions(); seedExecutionContext(database); recordExecutionDecisionFact(receipt("old", 0), { ...database, now: 0 }); recordExecutionDecisionFact(receipt("new", RETENTION_MS + 1), { ...database, now: RETENTION_MS + 1, limits: { maxRows: 10, pruneBatchRows: 1 }, }); expect( pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 10, now: RETENTION_MS + 1, database, }).receipts.map((item) => item.receiptId), ).toEqual(["new"]); expect( openOpenClawStateDatabase(database) .db.prepare("SELECT COUNT(*) AS count FROM execution_decision_facts") .get(), ).toEqual({ count: 1 }); }); it("caps retained facts without accepting a non-identical receipt id", () => { const database = databaseOptions(); seedExecutionContext(database); for (const [index, id] of ["one", "two", "three"].entries()) { recordExecutionDecisionFact(receipt(id, 100 + index), { ...database, now: 100 + index, limits: { maxRows: 2, pruneBatchRows: 1 }, }); } expect( pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 10, now: 200, database, }).receipts.map((item) => item.receiptId), ).toEqual(["two", "three"]); }); it("turns corrupt retained payloads into bounded unknown receipts", () => { const database = databaseOptions(); seedExecutionContext(database); const context: ExecutionIdentityContextV1 = { schemaVersion: 1, contextId: "context-1", executionId: "execution-1", runId: "run-1", createdAt: 50, trustDomain: { kind: "gateway-cell", domainRef: "domain-1", state: "present" }, invoker: { state: "absent" }, ingress: { kind: "local-cli", boundary: "agent-command.local", state: "present" }, agentPrincipal: { kind: "agent", domainRef: "domain-1", principalRef: "agent-main" }, agentDefinition: { definitionRef: "main", state: "present" }, runtimeInstance: { runtimeRef: "runtime-1", kind: "embedded", state: "present" }, applicableGrants: [], assurance: [], coverageState: "unattributed", missingEvidence: [], }; recordExecutionDecisionFact(receipt("corrupt"), { ...database, now: 100 }); openOpenClawStateDatabase(database) .db.prepare("UPDATE execution_decision_facts SET receipt_json = ? WHERE receipt_id = ?") .run("{", "corrupt"); const page = pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 10, now: 100, database, }); expect(page.entries).toEqual([ { selectorId: "decision-fact:1", receipt: expect.objectContaining({ receiptId: "corrupt", decision: { outcome: "unknown", reasonCode: "decision_fact_record_corrupt" }, enforcement: expect.objectContaining({ coverageState: "unknown" }), missingEvidence: ["decision.fact.valid"], }), }, ]); expect( summarizeExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, now: 100, database, }), ).toEqual({ count: 1, coverageState: "unknown", missingEvidence: ["decision.fact.valid"], }); expect( presentExecutionDecisionReceipts({ context, decisionCursor: "g:0:0", decisionLimit: 10, options: { ...database, now: 100 }, }), ).toMatchObject({ coverage: { state: "unknown", missingEvidence: expect.arrayContaining(["decision.display_provenance"]), }, decisions: [{ decision: { outcome: "unknown", reasonCode: "decision_fact_record_corrupt" } }], decisionDisplays: [{ selectorId: "decision-fact:1" }], }); }); it("does not materialize an oversized retained fact payload", () => { const database = databaseOptions(); const context = seedExecutionContext(database); recordExecutionDecisionFact(receipt("oversized"), { ...database, now: 100 }); const db = openOpenClawStateDatabase(database).db; db.exec("PRAGMA ignore_check_constraints = ON"); db.prepare("UPDATE execution_decision_facts SET receipt_json = ? WHERE receipt_id = ?").run( "x".repeat(20_000), "oversized", ); const page = pageExecutionDecisionFactsForContext({ context: { contextId: "context-1", executionId: "execution-1", runId: "run-1" }, limit: 1, now: 100, database, }); expect(page.entries).toEqual([ { selectorId: "decision-fact:1", receipt: expect.objectContaining({ decision: { outcome: "unknown", reasonCode: "decision_fact_payload_bounded" }, missingEvidence: ["decision.fact.payload_bounded"], }), }, ]); const result = presentExecutionDecisionReceipts({ context, decisionCursor: "g:0:0", decisionLimit: 1, options: { ...database, now: 100 }, }); expect(result.decisions).toHaveLength(1); expect(result.decisionDisplays).toEqual([ expect.objectContaining({ selectorId: "decision-fact:1" }), ]); }); });