Download src/audit/execution-identity-context.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 23.5 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/audit/execution-identity-context.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/audit/execution-identity-context.ts
-
curl -L -o execution-identity-context.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/audit/execution-identity-context.ts
23.5 kB
| /** Immutable execution identity context storage and run-admission projection. */ | |
| import type { DatabaseSync } from "node:sqlite"; | |
| import type { Selectable } from "kysely"; | |
| import type { ExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/index.js"; | |
| import { validateExecutionIdentityContextV1 } from "../../packages/gateway-protocol/src/index.js"; | |
| import { hasOperatorApprovalReceiptsForRun } from "../gateway/operator-approval-store.js"; | |
| import { | |
| executeSqliteQuerySync, | |
| executeSqliteQueryTakeFirstSync, | |
| getNodeSqliteKysely, | |
| } from "../infra/kysely-sync.js"; | |
| import { normalizeSqliteNumber } from "../infra/sqlite-number.js"; | |
| import { withExistingOpenClawStateDatabaseReadOnly } from "../state/openclaw-state-db-readonly.js"; | |
| import { tableExists } from "../state/openclaw-state-db-schema-helpers.js"; | |
| import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js"; | |
| import { | |
| openOpenClawStateDatabase, | |
| runOpenClawStateWriteTransaction, | |
| type OpenClawStateDatabaseOptions, | |
| } from "../state/openclaw-state-db.js"; | |
| import { createOpenClawStateSchemaEnsurer } from "../state/openclaw-state-feature-schema.js"; | |
| import { clearAuditIdentityKeyCacheForDatabase } from "./audit-identity.js"; | |
| import { hasExecutionDecisionFactsForRun } from "./execution-decision-facts.js"; | |
| import { | |
| presentExecutionDecisionReceipts, | |
| type InternalAuditRunInspectResult, | |
| } from "./execution-decision-receipts.js"; | |
| import { | |
| parseExecutionIdentityAdmissionEnvelope, | |
| parseExecutionIdentityAdmissionWork, | |
| type ExecutionIdentityAdmissionToken, | |
| } from "./execution-identity-admission.js"; | |
| import { | |
| buildExecutionIdentityContext, | |
| ensureBoundedExecutionIdentityRef, | |
| freezeExecutionIdentityContext, | |
| } from "./execution-identity-context-build.js"; | |
| type ExecutionIdentityDatabase = Pick< | |
| OpenClawStateKyselyDatabase, | |
| "audit_events" | "execution_identity_contexts" | |
| >; | |
| type ExecutionIdentityRow = Selectable<OpenClawStateKyselyDatabase["execution_identity_contexts"]>; | |
| const EXECUTION_IDENTITY_CONTEXT_MAX_BYTES = 16 * 1024; | |
| const EXECUTION_IDENTITY_CONTEXT_RETENTION_MS = 30 * 24 * 60 * 60_000; | |
| const EXECUTION_IDENTITY_CONTEXT_MAX_ROWS = 100_000; | |
| const EXECUTION_IDENTITY_CONTEXT_PRUNE_BATCH_ROWS = 1_024; | |
| const EXECUTION_IDENTITY_HMAC_REF_RE = /^hmac-sha256:v1:[a-f0-9]{32}:[a-f0-9]{64}$/u; | |
| const ensureExecutionIdentityContextSchema = createOpenClawStateSchemaEnsurer({ | |
| table: "execution_identity_contexts", | |
| endMarker: " ON execution_identity_contexts (run_id, created_at, execution_id);\n", | |
| operationLabel: "audit.execution-identity.schema.ensure", | |
| }); | |
| type ExecutionIdentityStoreOptions = OpenClawStateDatabaseOptions & { | |
| now?: number; | |
| limits?: { | |
| maxRows: number; | |
| pruneBatchRows: number; | |
| }; | |
| }; | |
| type ExecutionIdentityReadOptions = OpenClawStateDatabaseOptions & { | |
| now?: number; | |
| }; | |
| type ExecutionIdentityContextReadResult = | |
| | { status: "found"; context: ExecutionIdentityContextV1 } | |
| | { status: "expired"; runId: string } | |
| | { status: "missing" } | |
| | { status: "corrupt"; runId: string; reasonCode: "identity_context_corrupt" }; | |
| function executionIdentityDb(db: DatabaseSync) { | |
| return getNodeSqliteKysely<ExecutionIdentityDatabase>(db); | |
| } | |
| function parseExecutionIdentityRow(row: ExecutionIdentityRow): ExecutionIdentityContextV1 { | |
| if ( | |
| typeof row.context_json !== "string" || | |
| Buffer.byteLength(row.context_json, "utf8") !== normalizeSqliteNumber(row.context_bytes) || | |
| Buffer.byteLength(row.context_json, "utf8") > EXECUTION_IDENTITY_CONTEXT_MAX_BYTES | |
| ) { | |
| throw new Error("invalid context payload bounds"); | |
| } | |
| const parsed = JSON.parse(row.context_json) as unknown; | |
| if (!validateExecutionIdentityContextV1(parsed)) { | |
| throw new Error("invalid context payload schema"); | |
| } | |
| if ( | |
| parsed.contextId !== row.context_id || | |
| parsed.executionId !== row.execution_id || | |
| parsed.runId !== row.run_id || | |
| parsed.createdAt !== normalizeSqliteNumber(row.created_at) || | |
| parsed.coverageState !== row.coverage_state || | |
| JSON.stringify(parsed) !== row.context_json || | |
| !EXECUTION_IDENTITY_HMAC_REF_RE.test(parsed.trustDomain.domainRef) || | |
| !EXECUTION_IDENTITY_HMAC_REF_RE.test(parsed.runtimeInstance.runtimeRef) | |
| ) { | |
| throw new Error("context payload disagrees with indexed columns"); | |
| } | |
| return freezeExecutionIdentityContext(parsed); | |
| } | |
| function readRowByExecutionId( | |
| db: DatabaseSync, | |
| executionId: string, | |
| ): ExecutionIdentityRow | undefined { | |
| return executeSqliteQueryTakeFirstSync( | |
| db, | |
| executionIdentityDb(db) | |
| .selectFrom("execution_identity_contexts") | |
| .selectAll() | |
| .where("execution_id", "=", executionId), | |
| ); | |
| } | |
| function readRowsByRunId( | |
| db: DatabaseSync, | |
| runId: string, | |
| now: number, | |
| offset: number, | |
| limit: number, | |
| ): ExecutionIdentityRow[] { | |
| return executeSqliteQuerySync( | |
| db, | |
| executionIdentityDb(db) | |
| .selectFrom("execution_identity_contexts") | |
| .selectAll() | |
| .where("run_id", "=", runId) | |
| .where("created_at", ">=", now - EXECUTION_IDENTITY_CONTEXT_RETENTION_MS) | |
| .orderBy("created_at", "asc") | |
| .orderBy("execution_id", "asc") | |
| .offset(offset) | |
| .limit(limit), | |
| ).rows; | |
| } | |
| function deleteExpiredExecutionIdentityContexts(db: DatabaseSync, now: number, limit: number) { | |
| const kysely = executionIdentityDb(db); | |
| const expiredIds = kysely | |
| .selectFrom("execution_identity_contexts") | |
| .select("context_id") | |
| .where("created_at", "<", now - EXECUTION_IDENTITY_CONTEXT_RETENTION_MS) | |
| .orderBy("created_at", "asc") | |
| .orderBy("context_id", "asc") | |
| .limit(limit); | |
| return executeSqliteQuerySync( | |
| db, | |
| kysely.deleteFrom("execution_identity_contexts").where("context_id", "in", expiredIds), | |
| ); | |
| } | |
| function pruneExecutionIdentityContextsAfterInsert( | |
| db: DatabaseSync, | |
| now: number, | |
| limits: { maxRows: number; pruneBatchRows: number }, | |
| ): void { | |
| const kysely = executionIdentityDb(db); | |
| const expired = deleteExpiredExecutionIdentityContexts(db, now, limits.pruneBatchRows); | |
| const expiredCount = Number(expired.numAffectedRows ?? 0n); | |
| const remainingPruneBudget = Math.max(0, limits.pruneBatchRows - expiredCount); | |
| if (remainingPruneBudget > 0) { | |
| // Derive overflow from committed rows inside this transaction. A process-local | |
| // count misses writes from the Gateway worker or a concurrent direct CLI. | |
| const retainedIds = kysely | |
| .selectFrom("execution_identity_contexts") | |
| .select("context_id") | |
| .orderBy("created_at", "desc") | |
| .orderBy("context_id", "desc") | |
| .limit(limits.maxRows); | |
| const oldestOverflowIds = kysely | |
| .selectFrom("execution_identity_contexts") | |
| .select("context_id") | |
| .where("context_id", "not in", retainedIds) | |
| .orderBy("created_at", "asc") | |
| .orderBy("context_id", "asc") | |
| .limit(remainingPruneBudget); | |
| executeSqliteQuerySync( | |
| db, | |
| kysely.deleteFrom("execution_identity_contexts").where("context_id", "in", oldestOverflowIds), | |
| ); | |
| } | |
| } | |
| /** Delete one bounded batch during the existing audit startup/hourly maintenance tick. */ | |
| export function pruneExpiredExecutionIdentityContexts( | |
| params: { | |
| now?: number; | |
| database?: OpenClawStateDatabaseOptions; | |
| } = {}, | |
| ): number { | |
| const databaseOptions = params.database ?? {}; | |
| const database = openOpenClawStateDatabase(databaseOptions); | |
| // Maintenance must not create opt-in storage. First capture owns schema creation; | |
| // once the table exists, cleanup remains active even after collection is disabled. | |
| if (!tableExists(database.db, "execution_identity_contexts")) { | |
| return 0; | |
| } | |
| return runOpenClawStateWriteTransaction( | |
| ({ db }) => { | |
| const deleted = deleteExpiredExecutionIdentityContexts( | |
| db, | |
| params.now ?? Date.now(), | |
| EXECUTION_IDENTITY_CONTEXT_PRUNE_BATCH_ROWS, | |
| ); | |
| return Number(deleted.numAffectedRows ?? 0n); | |
| }, | |
| { ...databaseOptions, database }, | |
| { operationLabel: "audit.execution-identity.context.maintenance" }, | |
| ); | |
| } | |
| /** Worker-owned canonicalization and persistence for one accepted admission envelope. */ | |
| function persistExecutionIdentityAdmissionEnvelope( | |
| input: unknown, | |
| options: ExecutionIdentityStoreOptions = {}, | |
| ): ExecutionIdentityContextV1 { | |
| // Structured clone removes the admission-side freeze. Revalidate all bounds | |
| // before schema/key access so malformed messages never reach persistence. | |
| const envelope = parseExecutionIdentityAdmissionEnvelope(input); | |
| ensureExecutionIdentityContextSchema(options); | |
| const executionId = envelope.executionId; | |
| const opened = openOpenClawStateDatabase(options); | |
| // HMAC lookup/key creation and canonical serialization finish before BEGIN. | |
| // The transaction only rereads the authoritative row and synchronously commits. | |
| const plannedContext = buildExecutionIdentityContext(opened.db, envelope, { | |
| contextId: envelope.contextId, | |
| createdAt: envelope.createdAt, | |
| }); | |
| const plannedContextJson = JSON.stringify(plannedContext); | |
| let transactionDatabase: DatabaseSync | undefined; | |
| try { | |
| return runOpenClawStateWriteTransaction( | |
| ({ db }) => { | |
| transactionDatabase = db; | |
| const existing = readRowByExecutionId(db, executionId); | |
| if (existing) { | |
| const context = parseExecutionIdentityRow(existing); | |
| // Full canonical bytes, including the captured ID and timestamp, own replay identity. | |
| // Never rewrite a newly captured envelope to resemble the retained execution context. | |
| if (plannedContextJson !== existing.context_json) { | |
| throw new Error("execution identity context conflict for execution"); | |
| } | |
| return context; | |
| } | |
| executeSqliteQuerySync( | |
| db, | |
| executionIdentityDb(db) | |
| .insertInto("execution_identity_contexts") | |
| .values({ | |
| context_id: plannedContext.contextId, | |
| execution_id: plannedContext.executionId, | |
| run_id: plannedContext.runId, | |
| created_at: plannedContext.createdAt, | |
| coverage_state: plannedContext.coverageState, | |
| context_bytes: Buffer.byteLength(plannedContextJson, "utf8"), | |
| context_json: plannedContextJson, | |
| }), | |
| ); | |
| pruneExecutionIdentityContextsAfterInsert( | |
| db, | |
| options.now ?? Date.now(), | |
| options.limits ?? { | |
| maxRows: EXECUTION_IDENTITY_CONTEXT_MAX_ROWS, | |
| pruneBatchRows: EXECUTION_IDENTITY_CONTEXT_PRUNE_BATCH_ROWS, | |
| }, | |
| ); | |
| return plannedContext; | |
| }, | |
| options, | |
| { operationLabel: "audit.execution-identity.context.record" }, | |
| ); | |
| } catch (error) { | |
| if (transactionDatabase) { | |
| clearAuditIdentityKeyCacheForDatabase(transactionDatabase); | |
| } | |
| throw error; | |
| } | |
| } | |
| /** A durable recovery retry may only confirm the originally captured execution. */ | |
| function verifyExecutionIdentityAdmissionRetry( | |
| token: ExecutionIdentityAdmissionToken, | |
| options: ExecutionIdentityReadOptions = {}, | |
| ): ExecutionIdentityContextV1 { | |
| const { db } = openOpenClawStateDatabase(options); | |
| if (!tableExists(db, "execution_identity_contexts")) { | |
| throw new Error("execution identity recovery evidence unavailable"); | |
| } | |
| const existing = readRowByExecutionId(db, token.executionId); | |
| if (!existing) { | |
| // Never reconstruct identity from the later runtime after an ambiguous restart. | |
| throw new Error("execution identity recovery evidence unavailable"); | |
| } | |
| const context = parseExecutionIdentityRow(existing); | |
| if ( | |
| context.contextId !== token.contextId || | |
| context.executionId !== token.executionId || | |
| context.runId !== token.runId || | |
| context.createdAt !== token.createdAt | |
| ) { | |
| throw new Error("execution identity context conflict for execution"); | |
| } | |
| return context; | |
| } | |
| /** Worker-owned persistence/verification for one accepted bounded queue item. */ | |
| export function processExecutionIdentityAdmissionWork( | |
| input: unknown, | |
| options: ExecutionIdentityStoreOptions = {}, | |
| ): ExecutionIdentityContextV1 { | |
| const work = parseExecutionIdentityAdmissionWork(input); | |
| return work.kind === "capture" | |
| ? persistExecutionIdentityAdmissionEnvelope(work.envelope, options) | |
| : verifyExecutionIdentityAdmissionRetry(work.token, options); | |
| } | |
| /** Read one exact execution while turning malformed rows into typed diagnostics. */ | |
| function readExecutionIdentityContextByExecutionId( | |
| executionId: string, | |
| options: ExecutionIdentityReadOptions = {}, | |
| ): ExecutionIdentityContextReadResult { | |
| const normalizedExecutionId = ensureBoundedExecutionIdentityRef(executionId, "execution id"); | |
| return ( | |
| withExistingOpenClawStateDatabaseReadOnly(({ db }) => { | |
| if (!tableExists(db, "execution_identity_contexts")) { | |
| return { status: "missing" } as const; | |
| } | |
| const row = readRowByExecutionId(db, normalizedExecutionId); | |
| if (!row) { | |
| return { status: "missing" } as const; | |
| } | |
| const createdAt = normalizeSqliteNumber(row.created_at); | |
| if ( | |
| createdAt !== undefined && | |
| createdAt < (options.now ?? Date.now()) - EXECUTION_IDENTITY_CONTEXT_RETENTION_MS | |
| ) { | |
| // The indexed timestamp may explain availability, but expired context JSON | |
| // must never be parsed or projected while bounded maintenance catches up. | |
| return { status: "expired", runId: row.run_id } as const; | |
| } | |
| try { | |
| return { status: "found", context: parseExecutionIdentityRow(row) } as const; | |
| } catch { | |
| return { | |
| status: "corrupt", | |
| runId: row.run_id, | |
| reasonCode: "identity_context_corrupt", | |
| } as const; | |
| } | |
| }, options) ?? { status: "missing" } | |
| ); | |
| } | |
| function unavailableResult(params: { | |
| selector: { runId: string } | { executionId: string }; | |
| resolvedRunId?: string; | |
| runStatus: "known" | "unknown"; | |
| state: "unknown" | "unsupported"; | |
| reasonCode: string; | |
| missingEvidence: string[]; | |
| remediation: Array<{ code: string; text: string }>; | |
| }): InternalAuditRunInspectResult { | |
| const run: InternalAuditRunInspectResult["run"] = | |
| "executionId" in params.selector | |
| ? { | |
| executionId: params.selector.executionId, | |
| ...(params.resolvedRunId ? { runId: params.resolvedRunId } : {}), | |
| status: params.runStatus, | |
| } | |
| : { | |
| runId: params.resolvedRunId ?? params.selector.runId, | |
| status: params.runStatus, | |
| }; | |
| return { | |
| schemaVersion: 1, | |
| run, | |
| identity: { | |
| state: params.state, | |
| reasonCode: params.reasonCode, | |
| missingEvidence: params.missingEvidence, | |
| remediation: params.remediation, | |
| }, | |
| decisions: [], | |
| decisionDisplays: [], | |
| coverage: { state: params.state, missingEvidence: params.missingEvidence }, | |
| }; | |
| } | |
| function unavailableIdentityContext( | |
| selector: { runId: string } | { executionId: string }, | |
| remediation: { code: string; text: string }, | |
| resolvedRunId?: string, | |
| ): InternalAuditRunInspectResult { | |
| return unavailableResult({ | |
| selector, | |
| resolvedRunId, | |
| runStatus: "known", | |
| state: "unsupported", | |
| reasonCode: "identity_context_unavailable", | |
| missingEvidence: ["identity.context"], | |
| remediation: [remediation], | |
| }); | |
| } | |
| function inspectExactExecution( | |
| params: { executionId: string; decisionCursor?: string; decisionLimit?: number }, | |
| options: ExecutionIdentityReadOptions, | |
| ): InternalAuditRunInspectResult { | |
| const executionId = ensureBoundedExecutionIdentityRef(params.executionId, "execution id"); | |
| const selector = { executionId }; | |
| const contextResult = readExecutionIdentityContextByExecutionId(executionId, options); | |
| if (contextResult.status === "found") { | |
| return presentExecutionDecisionReceipts({ | |
| context: contextResult.context, | |
| decisionCursor: params.decisionCursor, | |
| decisionLimit: params.decisionLimit, | |
| options, | |
| }); | |
| } | |
| if (contextResult.status === "corrupt") { | |
| return unavailableResult({ | |
| selector, | |
| resolvedRunId: contextResult.runId, | |
| runStatus: "known", | |
| state: "unknown", | |
| reasonCode: contextResult.reasonCode, | |
| missingEvidence: ["identity.context.valid"], | |
| remediation: [ | |
| { | |
| code: "inspect_state_integrity", | |
| text: "Run openclaw doctor and inspect the shared state database before trusting this execution.", | |
| }, | |
| ], | |
| }); | |
| } | |
| if (contextResult.status === "expired") { | |
| return unavailableIdentityContext( | |
| selector, | |
| { | |
| code: "run_again_after_expiry", | |
| text: "This execution's identity context is outside the 30-day retention window; run the operation again to record a new context.", | |
| }, | |
| contextResult.runId, | |
| ); | |
| } | |
| return unavailableResult({ | |
| selector, | |
| runStatus: "unknown", | |
| state: "unknown", | |
| reasonCode: "execution_not_found", | |
| missingEvidence: ["identity.context"], | |
| remediation: [ | |
| { | |
| code: "verify_execution_id", | |
| text: "Verify the exact execution id; absence of best-effort identity evidence is not proof that no run occurred.", | |
| }, | |
| ], | |
| }); | |
| } | |
| function hasAnyRunContext(db: DatabaseSync, runId: string): boolean { | |
| return Boolean( | |
| executeSqliteQueryTakeFirstSync( | |
| db, | |
| executionIdentityDb(db) | |
| .selectFrom("execution_identity_contexts") | |
| .select("context_id") | |
| .where("run_id", "=", runId) | |
| .limit(1), | |
| ), | |
| ); | |
| } | |
| function hasRetainedAuditRun(db: DatabaseSync, runId: string, now: number): boolean { | |
| if (!tableExists(db, "audit_events")) { | |
| return false; | |
| } | |
| return Boolean( | |
| executeSqliteQueryTakeFirstSync( | |
| db, | |
| executionIdentityDb(db) | |
| .selectFrom("audit_events") | |
| .select("sequence") | |
| .where("run_id", "=", runId) | |
| .where("occurred_at", ">=", now - EXECUTION_IDENTITY_CONTEXT_RETENTION_MS) | |
| .where("kind", "!=", "message") | |
| .limit(1), | |
| ), | |
| ); | |
| } | |
| function inspectRunSelector( | |
| params: { | |
| runId: string; | |
| executionOffset?: number; | |
| executionLimit?: number; | |
| decisionCursor?: string; | |
| decisionLimit?: number; | |
| }, | |
| options: ExecutionIdentityReadOptions, | |
| ): InternalAuditRunInspectResult { | |
| const runId = ensureBoundedExecutionIdentityRef(params.runId, "run id"); | |
| const now = options.now ?? Date.now(); | |
| const inspected = withExistingOpenClawStateDatabaseReadOnly< | |
| InternalAuditRunInspectResult | undefined | |
| >(({ db }) => { | |
| const firstMatches = tableExists(db, "execution_identity_contexts") | |
| ? readRowsByRunId(db, runId, now, 0, 2) | |
| : []; | |
| if (firstMatches.length === 1) { | |
| let context: ExecutionIdentityContextV1; | |
| try { | |
| context = parseExecutionIdentityRow(firstMatches[0]!); | |
| } catch { | |
| return unavailableResult({ | |
| selector: { runId }, | |
| runStatus: "known", | |
| state: "unknown", | |
| reasonCode: "identity_context_corrupt", | |
| missingEvidence: ["identity.context.valid"], | |
| remediation: [ | |
| { | |
| code: "inspect_state_integrity", | |
| text: "Run openclaw doctor and inspect the shared state database before trusting this run.", | |
| }, | |
| ], | |
| }); | |
| } | |
| return presentExecutionDecisionReceipts({ | |
| context, | |
| decisionCursor: params.decisionCursor, | |
| decisionLimit: params.decisionLimit, | |
| options, | |
| }); | |
| } | |
| if (firstMatches.length > 1) { | |
| const offset = params.executionOffset ?? 0; | |
| const limit = params.executionLimit ?? 50; | |
| const page = readRowsByRunId(db, runId, now, offset, limit + 1); | |
| const candidates = page.slice(0, limit).map((row) => ({ | |
| executionId: row.execution_id, | |
| contextId: row.context_id, | |
| createdAt: normalizeSqliteNumber(row.created_at) ?? 0, | |
| })); | |
| return { | |
| schemaVersion: 1, | |
| run: { runId, status: "known" }, | |
| identity: { | |
| state: "ambiguous", | |
| reasonCode: "execution_selection_required", | |
| candidates, | |
| missingEvidence: ["execution.selection"], | |
| remediation: [ | |
| { | |
| code: "select_execution_id", | |
| text: "Select one candidate with openclaw audit --execution <id> --explain.", | |
| }, | |
| ], | |
| }, | |
| decisions: [], | |
| decisionDisplays: [], | |
| coverage: { state: "unknown", missingEvidence: ["execution.selection"] }, | |
| ...(page.length > limit ? { nextExecutionCursor: String(offset + limit) } : {}), | |
| }; | |
| } | |
| if ( | |
| hasOperatorApprovalReceiptsForRun({ runId, nowMs: now, databaseOptions: options }) || | |
| hasExecutionDecisionFactsForRun({ runId, now, database: options }) | |
| ) { | |
| return unavailableResult({ | |
| selector: { runId }, | |
| runStatus: "known", | |
| state: "unknown", | |
| reasonCode: "decision_context_link_missing", | |
| missingEvidence: ["identity.context", "decision.context_link"], | |
| remediation: [ | |
| { | |
| code: "record_new_identity_context", | |
| text: "Confirm execution identity collection is enabled, then run and request the action again to record a linked context.", | |
| }, | |
| ], | |
| }); | |
| } | |
| if (tableExists(db, "execution_identity_contexts") && hasAnyRunContext(db, runId)) { | |
| return unavailableIdentityContext( | |
| { runId }, | |
| { | |
| code: "run_again_after_expiry", | |
| text: "This run's retained identity contexts are outside the 30-day window; run the operation again to record a new execution.", | |
| }, | |
| ); | |
| } | |
| try { | |
| if (hasRetainedAuditRun(db, runId, now)) { | |
| return unavailableIdentityContext( | |
| { runId }, | |
| { | |
| code: "record_new_identity_context", | |
| text: "Confirm audit collection is enabled and the Gateway is current, then run the operation again to record a new execution context.", | |
| }, | |
| ); | |
| } | |
| } catch { | |
| return unavailableResult({ | |
| selector: { runId }, | |
| runStatus: "unknown", | |
| state: "unknown", | |
| reasonCode: "run_evidence_unreadable", | |
| missingEvidence: ["run.record", "identity.context"], | |
| remediation: [ | |
| { | |
| code: "inspect_state_integrity", | |
| text: "Run openclaw doctor and retry the run inspection.", | |
| }, | |
| ], | |
| }); | |
| } | |
| return undefined; | |
| }, options); | |
| if (inspected) { | |
| return inspected; | |
| } | |
| return unavailableResult({ | |
| selector: { runId }, | |
| runStatus: "unknown", | |
| state: "unknown", | |
| reasonCode: "run_not_found", | |
| missingEvidence: ["run.record", "identity.context"], | |
| remediation: [ | |
| { | |
| code: "verify_run_id", | |
| text: "Verify the run id; absence of best-effort audit activity is not proof of no run.", | |
| }, | |
| ], | |
| }); | |
| } | |
| /** Inspect one exact execution or discover bounded executions for a run correlation. */ | |
| export function inspectExecutionIdentityRun( | |
| params: | |
| | { | |
| runId: string; | |
| executionOffset?: number; | |
| executionLimit?: number; | |
| decisionCursor?: string; | |
| decisionLimit?: number; | |
| } | |
| | { executionId: string; decisionCursor?: string; decisionLimit?: number }, | |
| options: ExecutionIdentityReadOptions = {}, | |
| ): InternalAuditRunInspectResult { | |
| return "executionId" in params | |
| ? inspectExactExecution(params, options) | |
| : inspectRunSelector(params, options); | |
| } | |