import { createHash, fs, path } from '../dependencies.mjs'; import { isPlainObject } from '../harness/config.mjs'; import { isPathInside, readJson, readJsonl, resolveProjectRelative, } from '../harness/io.mjs'; import { runProcess } from '../harness/process.mjs'; import { seededGameHtml } from '../harness/project.mjs'; import { agentConversationPath, assertStructuredPlanContextSnapshot, inspectStructuredPlanSnapshot, readTargetDurableActions, targetMainTaskRunIds, } from '../harness/runtime.mjs'; import { commandRootErrorMarker, configFileName, gitCommitReflogMessage, gitSensitivePath, idempotentObservationTools, mainAgentId, patchedText, patchsetCreatedContent, patchsetCreatedMarker, patchsetCreatedPath, sentinelFileName, state, steerInstruction, supportedToolPlanProtocols, toolPlanNormalizationKinds, toolPlanProtocolAuditSafeFields, toolPlanProtocolErrorKinds, toolPlanProtocolErrorKindSet, toolPlanRepairAuditSafeFields, visibleText, } from '../runtime-state.mjs'; import { goalStaleActionReceiptMatchesOriginalAction } from '../suites/goal.mjs'; import { assert, codedError, hashValue } from './core.mjs'; export function validateMainRunToolPlanProtocols(records) { const protocols = records.filter( (record) => record.recordType === 'agent.runtime.tool_plan.protocol' && record.agentId === mainAgentId && record.runId === state.initialRunId, ); assert(protocols.length > 0, 'main-tool-plan-protocol-missing'); assert( protocols.every( (record) => supportedToolPlanProtocols.has(record.protocol) && hasSafeToolPlanAuditPayload(record), ), 'main-tool-plan-protocol-invalid', ); return protocols.length; } export function emptyToolPlanRepairCountsByProtocolErrorKind() { return Object.fromEntries( toolPlanProtocolErrorKinds.map((kind) => [kind, 0]), ); } export function isCatalogBoundToolPlanFunctionName(name, protocol) { if (!/^[a-z][a-z0-9_]{0,127}$/u.test(name)) return false; if (protocol === 'native_function') { return name === 'submit_agent_tool_plan'; } if (protocol !== 'native_runtime_tools') return false; return ( ['update_agent_plan', 'respond_to_user'].includes(name) || name.startsWith('runtime_tool_') ); } export function hasValidToolPlanProtocolCallProjection(record) { const isHash = (value) => /^[0-9a-f]{64}$/u.test(value); if ( !Number.isSafeInteger(record.functionCallCount) || record.functionCallCount < 0 || !Array.isArray(record.callIdSha256s) || record.callIdSha256s.length !== record.functionCallCount || !record.callIdSha256s.every(isHash) || new Set(record.callIdSha256s).size !== record.callIdSha256s.length || !Array.isArray(record.functionNames) || record.functionNames.length !== record.functionCallCount ) { return false; } if (record.protocol === 'text_json') { return record.functionCallCount === 0; } if (record.protocol === 'native_function') { return ( record.functionCallCount === 1 && record.functionNames.every((name) => isCatalogBoundToolPlanFunctionName(name, record.protocol), ) ); } return ( record.protocol === 'native_runtime_tools' && record.functionCallCount > 0 && record.functionNames.every((name) => isCatalogBoundToolPlanFunctionName(name, record.protocol), ) ); } export function hasSafeToolPlanAuditPayload(record) { const safeFields = record?.recordType === 'agent.runtime.tool_plan.protocol' ? toolPlanProtocolAuditSafeFields : record?.recordType === 'agent.runtime.tool_plan.repair' ? toolPlanRepairAuditSafeFields : null; const isHash = (value) => /^[0-9a-f]{64}$/u.test(value); if ( !safeFields || !isPlainObject(record) || Object.keys(record).length !== safeFields.size || Object.keys(record).some((field) => !safeFields.has(field)) || record.schemaVersion !== 'game-creator-agent-db.v1' || !Number.isSafeInteger(record.updatedAt) || record.updatedAt <= 0 || !['agentId', 'taskId', 'sessionId', 'runId', 'source'].every((field) => isNonEmptyString(record[field]), ) || !supportedToolPlanProtocols.has(record.protocol) || !Number.isSafeInteger(record.loopIteration) || record.loopIteration < 0 || !Number.isSafeInteger(record.repairAttempt) || record.repairAttempt < 0 || record.requestSlot !== `loop-${record.loopIteration}-repair-${record.repairAttempt}` || !isHash(record.responseFingerprint) || !isHash(record.providerRequestIdSha256) ) { return false; } if (record.recordType === 'agent.runtime.tool_plan.repair') { const hasCallIdentity = isHash(record.callIdSha256) && isHash(record.functionNameSha256); const hasNoCallIdentity = record.callIdSha256 == null && record.functionNameSha256 == null; return ( toolPlanProtocolErrorKindSet.has(record.protocolErrorKind) && Number.isSafeInteger(record.attempt) && record.attempt > 0 && record.attempt === record.repairAttempt + 1 && Number.isSafeInteger(record.maxAttempts) && record.maxAttempts >= record.attempt && isHash(record.protocolErrorSha256) && isHash(record.responsePreviewSha256) && ['protocolErrorChars', 'responsePreviewChars'].every( (field) => Number.isSafeInteger(record[field]) && record[field] >= 0, ) && (record.protocol === 'text_json' ? hasNoCallIdentity : hasCallIdentity) ); } if ( !hasValidToolPlanProtocolCallProjection(record) || !Number.isSafeInteger(record.responseIdChars) || record.responseIdChars < 0 || (record.responseIdSha256 == null ? record.responseIdChars !== 0 : !isHash(record.responseIdSha256) || record.responseIdChars === 0) || !Array.isArray(record.normalizationKinds) || !Number.isSafeInteger(record.normalizationCount) || record.normalizationCount < 0 || !Number.isSafeInteger(record.normalizedTextChars) || record.normalizedTextChars < 0 ) { return false; } return record.normalizationCount === 0 ? record.normalizationKinds.length === 0 && record.normalizedTextChars === 0 && record.normalizedTextSha256 == null : record.normalizationKinds.length > 0 && record.normalizationKinds.length <= toolPlanNormalizationKinds.size && new Set(record.normalizationKinds).size === record.normalizationKinds.length && record.normalizationKinds.every((kind) => toolPlanNormalizationKinds.has(kind), ) && record.normalizationCount >= record.normalizationKinds.length && record.normalizedTextChars > 0 && isHash(record.normalizedTextSha256); } export function collectToolPlanRepairAuditEvidence(audits) { const repairs = audits.filter( (record) => record.recordType === 'agent.runtime.tool_plan.repair', ); const countsByKind = emptyToolPlanRepairCountsByProtocolErrorKind(); const repairedLoops = new Set(); let secondRepairCount = 0; for (const repair of repairs) { assert( hasSafeToolPlanAuditPayload(repair) && toolPlanProtocolErrorKindSet.has(repair.protocolErrorKind) && isNonEmptyString(repair.agentId) && isNonEmptyString(repair.runId) && Number.isSafeInteger(repair.loopIteration) && repair.loopIteration >= 0 && Number.isSafeInteger(repair.attempt) && repair.attempt > 0 && Number.isSafeInteger(repair.maxAttempts) && repair.maxAttempts >= repair.attempt, 'tool-plan-repair-classification-metadata-invalid', ); countsByKind[repair.protocolErrorKind] += 1; repairedLoops.add( `${repair.agentId}\0${repair.runId}\0${repair.loopIteration}`, ); if (repair.attempt === 2) secondRepairCount += 1; } assert( sumObjectValues(countsByKind) === repairs.length, 'tool-plan-repair-classification-total-mismatch', ); return { toolPlanRepairCount: repairs.length, nativeRuntimeToolPlanRepairCount: repairs.filter( (record) => record.protocol === 'native_runtime_tools', ).length, toolPlanRepairedLoopCount: repairedLoops.size, toolPlanSecondRepairCount: secondRepairCount, toolPlanRepairCountsByProtocolErrorKind: countsByKind, toolPlanAuditPayloadLeakCount: audits.filter( (record) => !hasSafeToolPlanAuditPayload(record), ).length, }; } export function toolPlanRepairEvidenceHasNoFatalLocalRepair(evidence) { return ( evidence.toolPlanRepairCountsByProtocolErrorKind['catalog-binding'] === 0 ); } export function collectNativeRuntimeToolPlanProtocolEvidence(records) { const protocols = records.filter( (record) => record.recordType === 'agent.runtime.tool_plan.protocol' && record.agentId === mainAgentId && record.runId === state.initialRunId, ); const repairs = records.filter( (record) => record.recordType === 'agent.runtime.tool_plan.repair' && record.agentId === mainAgentId && record.runId === state.initialRunId, ); const audits = [...protocols, ...repairs]; return { toolPlanProtocolCount: protocols.length, nativeRuntimeToolPlanCount: protocols.filter( (record) => record.protocol === 'native_runtime_tools', ).length, wrapperToolPlanFallbackCount: audits.filter( (record) => record.protocol === 'native_function', ).length, textJsonToolPlanFallbackCount: audits.filter( (record) => record.protocol === 'text_json', ).length, ...collectToolPlanRepairAuditEvidence(audits), }; } export function validateNativeRuntimeToolPlanProtocolEvidence(records) { const protocolCount = validateMainRunToolPlanProtocols(records); const evidence = collectNativeRuntimeToolPlanProtocolEvidence(records); const protocols = records.filter( (record) => record.recordType === 'agent.runtime.tool_plan.protocol' && record.agentId === mainAgentId && record.runId === state.initialRunId, ); assert( evidence.toolPlanProtocolCount === protocolCount && evidence.nativeRuntimeToolPlanCount === protocolCount && evidence.nativeRuntimeToolPlanRepairCount === evidence.toolPlanRepairCount && evidence.wrapperToolPlanFallbackCount === 0 && evidence.textJsonToolPlanFallbackCount === 0 && evidence.toolPlanAuditPayloadLeakCount === 0 && toolPlanRepairEvidenceHasNoFatalLocalRepair(evidence) && protocols.every( (record) => hasValidToolPlanProtocolCallProjection(record) && Array.isArray(record.functionNames) && record.functionNames.length === record.functionCallCount && record.functionNames.every( (name) => isNonEmptyString(name) && name !== 'submit_agent_tool_plan', ), ), 'goal-native-tool-plan-protocol-required', ); return evidence; } export async function validateSameRunSteerEvidence({ agentDb, activity, contextBundle, conversationEntries, events, initial, output, runtimeState, taskSnapshot, }) { assert(state.steer, 'same-run-steer-state-missing'); assert( state.steer.providerInterrupted === true && state.steer.providerWaitStatus === 'running' && state.steer.providerWaitPhase === 'planning' && Number.isSafeInteger(state.steer.providerWaitUpdatedAt) && JSON.stringify(state.steer.taskRunIdsBefore) === JSON.stringify(state.steer.taskRunIdsAfter) && state.steer.taskRunIdsBefore.includes(state.initialRunId) && state.steer.taskRunSetHash === hashValue(JSON.stringify(state.steer.taskRunIdsBefore)) && Number.isSafeInteger(state.steer.planRevisionAtAcceptance) && runtimeState.planRevision > state.steer.planRevisionAtAcceptance, 'same-run-steer-task-queue-invalid', ); const finalMainRunIds = targetMainTaskRunIds( taskSnapshot, state.steer.taskIdentity, ); assert( JSON.stringify(finalMainRunIds) === JSON.stringify(state.steer.taskRunIdsBefore), 'same-run-steer-final-target-run-set-invalid', ); const ledgerPath = path.join( state.projectRoot, '.agent/runtime/steers', mainAgentId, `${state.initialRunId}.jsonl`, ); const ledger = await readJsonl(ledgerPath); const entryRecords = ledger.filter( (record) => record.steerId === state.steer.steerId, ); const closedRecords = ledger.filter( (record) => record.steerId == null && record.status === 'closed', ); assert( ledger.length === 5 && entryRecords.length === 4 && JSON.stringify(entryRecords.map((record) => record.status)) === JSON.stringify([ 'prepared', 'conversation-persisted', 'queued', 'applied', ]) && closedRecords.length === 1 && ledger.at(-1) === closedRecords[0], 'same-run-steer-ledger-lifecycle-invalid', ); const prepared = entryRecords[0]; const expectedMessageId = prepared.messageId; assert( isNonEmptyString(expectedMessageId) && entryRecords.every( (record) => record.schemaVersion === 'game-creator-runtime-steer.v1' && isNonEmptyString(record.projectId) && record.agentId === mainAgentId && record.taskId === initial.taskId && record.sessionId === state.initialSessionId && record.runId === state.initialRunId && record.source === initial.source && record.sequence === state.steer.sequence && record.messageId === expectedMessageId && record.instructionSha256 === state.steer.instructionSha256 && record.contentChars === [...steerInstruction].length && record.contentBytes === Buffer.byteLength(steerInstruction) && record.acceptedVia === 'cli', ) && prepared.instruction === steerInstruction && entryRecords.slice(1).every((record) => record.instruction == null) && Number.isSafeInteger(entryRecords.at(-1).appliedAt), 'same-run-steer-ledger-entry-invalid', ); const closed = closedRecords[0]; assert( closed.schemaVersion === prepared.schemaVersion && closed.projectId === prepared.projectId && closed.agentId === mainAgentId && closed.taskId === initial.taskId && closed.sessionId === state.initialSessionId && closed.runId === state.initialRunId && closed.source === initial.source && closed.sequence === state.steer.sequence && closed.steerId == null && closed.messageId == null && closed.instructionSha256 == null && closed.instruction == null && closed.contentChars === 0 && closed.contentBytes === 0 && closed.acceptedVia === 'runtime-finalization', 'same-run-steer-ledger-closed-invalid', ); const runtimeRefs = runtimeState.appliedSteerRefs ?? []; const contextRefs = contextBundle.appliedSteerRefs ?? []; assert( runtimeState.agentId === mainAgentId && runtimeState.sessionId === state.initialSessionId && runtimeState.runId === state.initialRunId && runtimeState.appliedSteerCursor === state.steer.sequence && runtimeState.queuedSteerCount === 0 && runtimeRefs.length === 1 && runtimeRefs[0].steerId === state.steer.steerId && runtimeRefs[0].sequence === state.steer.sequence && runtimeRefs[0].messageId === expectedMessageId && runtimeRefs[0].instructionSha256 === state.steer.instructionSha256 && runtimeRefs[0].contentChars === [...steerInstruction].length && contextBundle.appliedSteerCursor === runtimeState.appliedSteerCursor && JSON.stringify(contextRefs) === JSON.stringify(runtimeRefs), 'same-run-steer-runtime-state-invalid', ); const targetConversationPath = path.resolve( agentConversationPath(mainAgentId, state.initialSessionId), ); const steerMessages = conversationEntries.filter( ({ file, message }) => file === targetConversationPath && message.messageId === expectedMessageId, ); assert( steerMessages.length === 1 && steerMessages[0].message.role === 'user' && steerMessages[0].message.agentId === mainAgentId && steerMessages[0].message.content === steerInstruction && hashValue(steerMessages[0].message.content) === state.steer.instructionSha256, 'same-run-steer-conversation-invalid', ); const steerConversationAudits = agentDb.filter( (record) => record.recordType === 'conversation.message' && record.role === 'user' && record.agentId === mainAgentId && record.sessionId === state.initialSessionId && record.messageId === expectedMessageId, ); assert( steerConversationAudits.length === 1 && isNonEmptyString(steerConversationAudits[0].path) && path.resolve(resolveProjectRelative(steerConversationAudits[0].path)) === targetConversationPath, 'same-run-steer-conversation-audit-invalid', ); const indexedSteerAudits = agentDb .map((record, index) => ({ index, record })) .filter( ({ record }) => record.recordType === 'agent.runtime.steer' && record.agentId === mainAgentId && record.taskId === initial.taskId && record.sessionId === state.initialSessionId && record.runId === state.initialRunId && record.steerId === state.steer.steerId, ); const steerAudits = indexedSteerAudits.map(({ record }) => record); assert( steerAudits.length === 2 && JSON.stringify(steerAudits.map((record) => record.status)) === JSON.stringify(['queued', 'applied']) && steerAudits.every( (record) => record.sequence === state.steer.sequence && record.messageId === expectedMessageId && record.instructionSha256 === state.steer.instructionSha256 && record.contentChars === [...steerInstruction].length, ), 'same-run-steer-audit-invalid', ); const appliedAuditIndex = indexedSteerAudits.find( ({ record }) => record.status === 'applied', )?.index; assert( Number.isSafeInteger(appliedAuditIndex) && appliedAuditIndex >= state.steer.agentDbSequenceBefore, 'same-run-steer-applied-sequence-invalid', ); const postSteerPlanUpdates = agentDb .map((record, index) => ({ index, record })) .filter( ({ index, record }) => index > appliedAuditIndex && record.recordType === 'agent.runtime.plan_update' && record.agentId === mainAgentId && record.taskId === initial.taskId && record.sessionId === state.initialSessionId && record.runId === state.initialRunId, ); assert(postSteerPlanUpdates.length > 0, 'same-run-steer-replan-missing'); const firstPostSteerPlan = postSteerPlanUpdates[0].record; const completedBeforeSteer = new Set( state.steer.completedStepHashesAtAcceptance, ); const completedAfterSteer = new Set( firstPostSteerPlan.steps .filter((step) => step.status === 'completed') .map((step) => step.stepSha256), ); const incompleteAfterSteer = firstPostSteerPlan.steps .map((step, index) => ({ index, status: step.status, stepHash: step.stepSha256, })) .filter((step) => step.status !== 'completed'); assert( firstPostSteerPlan.planRevision > state.steer.planRevisionAtAcceptance && completedBeforeSteer.size > 0 && [...completedBeforeSteer].every((stepHash) => completedAfterSteer.has(stepHash), ) && incompleteAfterSteer.length > 0 && hashValue(JSON.stringify(incompleteAfterSteer)) !== state.steer.incompletePlanSignatureAtAcceptance, 'same-run-steer-incomplete-plan-not-reordered', ); const oldActionIds = new Set( state.steer.activeActionsAtAcceptance.map((action) => action.actionId), ); const oldPendingExecutionCount = agentDb .slice(state.steer.agentDbSequenceBefore) .filter( (record) => oldActionIds.has(record.actionId) && [ 'agent.runtime.tool_action.executing', 'agent.runtime.action_receipt', ].includes(record.recordType), ).length; const acceptanceWindowExecutionCount = agentDb .slice(state.steer.agentDbSequenceBefore, appliedAuditIndex + 1) .filter( (record) => record.recordType === 'agent.runtime.tool_action.executing' && record.agentId === mainAgentId && record.runId === state.initialRunId, ).length; const durableActionIdsAtAcceptance = new Set( state.steer.durableActionsAtAcceptance.map((action) => action.actionId), ); const oldPlanMaterializedActions = (await readTargetDurableActions()).filter( (action) => action.plannedSteerCursor < state.steer.sequence && !durableActionIdsAtAcceptance.has(action.actionId), ); assert( oldPendingExecutionCount === 0 && acceptanceWindowExecutionCount === 0 && oldPlanMaterializedActions.length === 0, 'same-run-steer-old-pending-action-executed', ); let preSteerSideEffectReplayCount = 0; for (const latched of state.steer.sideEffectReceiptsAtAcceptance) { const matches = agentDb.filter( (record) => record.recordType === 'agent.runtime.action_receipt' && record.agentId === mainAgentId && record.runId === state.initialRunId && record.actionId === latched.actionId && record.actionFingerprint === latched.actionFingerprint && record.tool === latched.tool && record.status === latched.status && hashValue(actionReceiptIdentity(record)) === latched.identityHash, ); if (matches.length > 1) preSteerSideEffectReplayCount += matches.length - 1; assert(matches.length === 1, 'same-run-steer-side-effect-receipt-changed'); } const finalRevision = await readJson( path.join(state.projectRoot, '.agent/runtime/project-revision.json'), ); assert( Number.isSafeInteger(state.steer.projectRevisionAtAcceptance) && finalRevision.revision >= state.steer.projectRevisionAtAcceptance && /^[0-9a-f]{64}$/u.test( state.steer.projectSideEffectFingerprintAtAcceptance ?? '', ) && preSteerSideEffectReplayCount === 0, 'same-run-steer-side-effect-snapshot-invalid', ); const publicInstructionLeakCount = countExactSecrets( Buffer.from( JSON.stringify([ taskSnapshot.all, events, agentDb, activity, output, runtimeState, ]), ), [steerInstruction], ); assert( publicInstructionLeakCount === 0, 'same-run-steer-public-instruction-leak', ); return { planRevisionAtAcceptance: state.steer.planRevisionAtAcceptance, sequence: state.steer.sequence, steerIdHash: state.steer.steerIdHash, messageId: expectedMessageId, messageIdHash: hashValue(expectedMessageId), instructionSha256: state.steer.instructionSha256, providerInterrupted: state.steer.providerInterrupted, providerPlanningWaitMatched: true, completedStepCountAtAcceptance: completedBeforeSteer.size, incompleteStepCountAtAcceptance: state.steer.incompleteStepsAtAcceptance.length, firstPostSteerPlanRevision: firstPostSteerPlan.planRevision, firstPostSteerIncompleteStepCount: incompleteAfterSteer.length, incompletePlanReordered: true, oldPendingActionCount: oldActionIds.size, oldPendingActionSetHash: hashValue( JSON.stringify( state.steer.activeActionsAtAcceptance.map((action) => [ action.actionId, action.actionFingerprint, action.tool, action.status, action.plannedSteerCursor, ]), ), ), oldPendingExecutionCount, oldPlanMaterializedActionCount: oldPlanMaterializedActions.length, acceptanceWindowExecutionCount, sideEffectReceiptCountAtAcceptance: state.steer.sideEffectReceiptsAtAcceptance.length, sideEffectSnapshotHash: hashValue( JSON.stringify( state.steer.sideEffectReceiptsAtAcceptance.map( (receipt) => receipt.identityHash, ), ), ), preSteerSideEffectReplayCount, ledgerRecordCount: ledger.length, appliedCount: entryRecords.filter((record) => record.status === 'applied') .length, closedCount: closedRecords.length, auditCount: steerAudits.length, taskRunCountBefore: state.steer.taskRunIdsBefore.length, taskRunCountAfter: state.steer.taskRunIdsAfter.length, taskRunSetHash: state.steer.taskRunSetHash, publicInstructionLeakCount, }; } export function validateStructuredPlanEvidence( records, runtimeState, contextBundle, ) { const indexedUpdates = records .map((record, index) => ({ index, record })) .filter( ({ record }) => record.recordType === 'agent.runtime.plan_update' && record.agentId === mainAgentId && record.sessionId === state.initialSessionId && record.runId === state.initialRunId, ); const updates = indexedUpdates.map(({ record }) => record); assert(updates.length >= 3, 'structured-plan-update-count-invalid'); const completed = new Set(); let regressionCount = 0; let previousPlanSignature = null; for (const [index, update] of updates.entries()) { assert( update.planRevision === index + 1 && Number.isSafeInteger(update.updatedAt) && /^[0-9a-f]{64}$/u.test(update.explanationSha256 ?? '') && Number.isInteger(update.explanationChars) && update.explanationChars > 0 && Array.isArray(update.steps) && update.steps.length >= 3 && update.steps.length <= 8, 'structured-plan-update-invalid', ); const planSignature = hashValue( JSON.stringify({ explanationSha256: update.explanationSha256, steps: update.steps, }), ); assert( planSignature !== previousPlanSignature, 'structured-plan-idempotent-revision-incremented', ); previousPlanSignature = planSignature; const stepNames = new Set(); let inProgressCount = 0; const byName = new Map(); for (const step of update.steps) { assert( /^[0-9a-f]{64}$/u.test(step.stepSha256 ?? '') && ['pending', 'in_progress', 'completed'].includes(step.status) && !stepNames.has(step.stepSha256), 'structured-plan-step-invalid', ); stepNames.add(step.stepSha256); byName.set(step.stepSha256, step.status); if (step.status === 'in_progress') inProgressCount += 1; } assert(inProgressCount <= 1, 'structured-plan-multiple-in-progress'); for (const step of completed) { if (byName.get(step) !== 'completed') regressionCount += 1; } for (const step of update.steps) { if (step.status === 'completed') completed.add(step.stepSha256); } } assert(regressionCount === 0, 'structured-plan-terminal-regression'); const timeline = validatePlanUpdateActionTimeline(records, indexedUpdates); const finalUpdate = updates.at(-1); assert( finalUpdate.steps.every((step) => step.status === 'completed'), 'structured-plan-final-not-completed', ); const finalSnapshot = inspectStructuredPlanSnapshot( runtimeState, 'final-structured-plan', ); assert( runtimeState.planRevision === finalUpdate.planRevision && createHash('sha256') .update(runtimeState.planExplanation) .digest('hex') === finalUpdate.explanationSha256 && Array.isArray(runtimeState.planSteps) && runtimeState.planSteps.length === finalUpdate.steps.length && runtimeState.planSteps.every( (step, index) => step.index === index && createHash('sha256').update(step.title).digest('hex') === finalUpdate.steps[index].stepSha256 && step.status === 'completed', ) && runtimeState.activePlanStepIndex === null, 'structured-plan-runtime-state-invalid', ); assertStructuredPlanContextSnapshot( runtimeState, contextBundle, 'structured-plan-final-context', ); const recovery = state.planRecovery; assert( recovery && Number.isSafeInteger(recovery.preKillRevision) && recovery.preKillRevision > 0 && Number.isSafeInteger(recovery.recoveredRevision) && recovery.recoveredRevision >= recovery.preKillRevision && Array.isArray(recovery.preKillCompletedStepHashes) && recovery.preKillCompletedStepHashes.length > 0 && recovery.preKillIncompleteStepCount > 0 && /^[0-9a-f]{64}$/u.test(recovery.preKillTerminalStepHash ?? '') && Array.isArray(recovery.recoveredCompletedStepHashes) && /^[0-9a-f]{64}$/u.test(recovery.recoveredTerminalStepHash ?? ''), 'structured-plan-recovery-state-invalid', ); const preKillUpdate = updates.find( (update) => update.planRevision === recovery.preKillRevision, ); const recoveredUpdate = updates.find( (update) => update.planRevision === recovery.recoveredRevision, ); assert( preKillUpdate && recoveredUpdate && preKillUpdate.steps.length >= 3 && preKillUpdate.steps.some((step) => step.status !== 'completed'), 'structured-plan-recovery-revision-missing', ); const preKillCompleted = preKillUpdate.steps .filter((step) => step.status === 'completed') .map((step) => step.stepSha256) .sort(); const recoveredCompleted = recoveredUpdate.steps .filter((step) => step.status === 'completed') .map((step) => step.stepSha256) .sort(); assert( JSON.stringify(preKillCompleted) === JSON.stringify(recovery.preKillCompletedStepHashes) && recovery.preKillIncompleteStepCount === preKillUpdate.steps.length - preKillCompleted.length && recovery.preKillTerminalStepHash === hashValue(JSON.stringify(preKillCompleted)) && JSON.stringify(recoveredCompleted) === JSON.stringify(recovery.recoveredCompletedStepHashes) && recovery.recoveredTerminalStepHash === hashValue(JSON.stringify(recoveredCompleted)) && preKillCompleted.every((stepHash) => recoveredCompleted.includes(stepHash), ) && recoveredCompleted.every((stepHash) => finalSnapshot.completedStepHashes.includes(stepHash), ), 'structured-plan-recovery-terminal-step-invalid', ); return { updateCount: updates.length, planRevision: runtimeState.planRevision, completedStepCount: runtimeState.planSteps.length, regressionCount, preKillRevision: recovery.preKillRevision, preKillCompletedStepCount: preKillCompleted.length, preKillIncompleteStepCount: recovery.preKillIncompleteStepCount, preKillTerminalStepHash: recovery.preKillTerminalStepHash, recoveredRevision: recovery.recoveredRevision, recoveredCompletedStepCount: recoveredCompleted.length, recoveredTerminalStepHash: recovery.recoveredTerminalStepHash, timelineUpdateCount: timeline.updateCount, timelineAnchoredUpdateCount: timeline.anchoredUpdateCount, completionTransitionCount: timeline.completionTransitionCount, completionObservationCount: timeline.completionObservationCount, prematureCompletionCount: timeline.prematureCompletionCount, timelineHash: timeline.timelineHash, }; } export function validatePlanUpdateActionTimeline(records, indexedUpdates) { const terminalActions = records .map((record, index) => ({ index, record })) .filter( ({ record }) => record.recordType === 'agent.runtime.tool_observation' && record.agentId === mainAgentId && record.runId === state.initialRunId && record.status !== 'waiting-for-confirmation' && isNonEmptyString(record.actionId) && isNonEmptyString(record.actionFingerprint) && isNonEmptyString(record.tool) && Number.isSafeInteger(record.updatedAt), ) .map(({ index: observationIndex, record: observation }) => { const receiptIndex = records.findIndex( (record) => record.recordType === 'agent.runtime.action_receipt' && record.agentId === mainAgentId && record.runId === state.initialRunId && record.actionId === observation.actionId && record.actionFingerprint === observation.actionFingerprint && record.tool === observation.tool && record.status === observation.status, ); const actionIndex = records.findIndex( (record) => record.agentId === mainAgentId && record.runId === state.initialRunId && record.actionId === observation.actionId && record.actionFingerprint === observation.actionFingerprint && record.tool === observation.tool && [ 'agent.runtime.tool_action.executing', 'agent.runtime.tool_confirmation_required', 'agent.runtime.tool_confirmation.approved', 'agent.runtime.tool_confirmation.rejected', 'agent.runtime.tool_action.observed', ].includes(record.recordType), ); const receipt = records[receiptIndex]; const action = records[actionIndex]; assert( actionIndex >= 0 && actionIndex < observationIndex && receiptIndex >= 0 && receiptIndex < observationIndex && Number.isSafeInteger(action.updatedAt) && Number.isSafeInteger(receipt.updatedAt) && action.updatedAt <= receipt.updatedAt && receipt.updatedAt <= observation.updatedAt, 'structured-plan-terminal-action-lifecycle-invalid', ); return { actionIdentityHash: hashValue( `${observation.actionId}\0${observation.actionFingerprint}\0${observation.tool}`, ), actionIndex, observation, observationIndex, receiptIndex, }; }); assert( duplicateCount( terminalActions.map((action) => action.observation.actionId), ) === 0, 'structured-plan-terminal-observation-duplicate', ); const appliedSteerIndexes = records .map((record, index) => ({ index, record })) .filter( ({ record }) => record.recordType === 'agent.runtime.steer' && record.agentId === mainAgentId && record.runId === state.initialRunId && record.status === 'applied', ) .map(({ index }) => index); const timeline = []; let anchoredUpdateCount = 0; let completionTransitionCount = 0; let completionObservationCount = 0; let prematureCompletionCount = 0; let previousCompleted = new Set(); let previousUpdateIndex = -1; for (const [updateIndex, indexedUpdate] of indexedUpdates.entries()) { const update = indexedUpdate.record; const completedNow = new Set( update.steps .filter((step) => step.status === 'completed') .map((step) => step.stepSha256), ); const newlyCompleted = [...completedNow].filter( (stepHash) => !previousCompleted.has(stepHash), ); const intervalActions = terminalActions.filter( (action) => action.observationIndex > previousUpdateIndex && action.observationIndex < indexedUpdate.index, ); const intervalSteerIndexes = appliedSteerIndexes.filter( (index) => index > previousUpdateIndex && index < indexedUpdate.index, ); if (updateIndex === 0) { assert( newlyCompleted.length === 0, 'structured-plan-initial-update-precompleted-step', ); } else { assert( intervalActions.length > 0 || intervalSteerIndexes.length > 0, 'structured-plan-update-without-action-or-steer-anchor', ); anchoredUpdateCount += 1; } if (newlyCompleted.length > intervalActions.length) { prematureCompletionCount += newlyCompleted.length - intervalActions.length; } assert( newlyCompleted.length <= intervalActions.length, 'structured-plan-completed-before-terminal-observation', ); const completionActions = newlyCompleted.length === 0 ? [] : intervalActions.slice(-newlyCompleted.length); for (const action of completionActions) { assert( action.observation.updatedAt <= update.updatedAt && action.receiptIndex < action.observationIndex && action.observationIndex < indexedUpdate.index, 'structured-plan-completion-timestamp-invalid', ); } completionTransitionCount += newlyCompleted.length; completionObservationCount += completionActions.length; timeline.push({ actionIdentityHashes: completionActions.map( (action) => action.actionIdentityHash, ), completedStepHashes: newlyCompleted.sort(), planRevision: update.planRevision, planSequence: indexedUpdate.index + 1, steerSequences: intervalSteerIndexes.map((index) => index + 1), terminalObservationSequences: completionActions.map( (action) => action.observationIndex + 1, ), updatedAt: update.updatedAt, }); previousCompleted = completedNow; previousUpdateIndex = indexedUpdate.index; } assert( completionTransitionCount > 0 && completionTransitionCount === completionObservationCount && prematureCompletionCount === 0, 'structured-plan-completion-observation-coverage-invalid', ); return { updateCount: indexedUpdates.length, anchoredUpdateCount, completionTransitionCount, completionObservationCount, prematureCompletionCount, timelineHash: hashValue(JSON.stringify(timeline)), }; } export function validateConfirmedActionLifecycles(records) { const indexed = records.map((record, index) => ({ record, index })); const approvals = indexed.filter( ({ record }) => record.recordType === 'agent.runtime.tool_confirmation.approved', ); const approvedActionIds = new Set( approvals.map(({ record }) => record.actionId), ); assert( state.confirmedActionIds.size > 0, 'confirmed-action-evidence-missing', ); assert( approvals.length === approvedActionIds.size && approvedActionIds.size === state.confirmedActionIds.size && [...approvedActionIds].every((actionId) => state.confirmedActionIds.has(actionId), ) && [...state.confirmedActionIds].every((actionId) => approvedActionIds.has(actionId), ), 'confirmed-action-set-mismatch', ); for (const actionId of state.confirmedActionIds) { const lifecycle = indexed.filter( ({ record }) => record.actionId === actionId, ); const waiting = lifecycle.filter( ({ record }) => record.recordType === 'agent.runtime.tool_observation' && record.status === 'waiting-for-confirmation', ); const observed = lifecycle.filter( ({ record }) => record.recordType === 'agent.runtime.tool_observation' && record.decision === 'approved' && record.status !== 'waiting-for-confirmation', ); const required = lifecycle.filter( ({ record }) => record.recordType === 'agent.runtime.tool_confirmation_required', ); const approved = lifecycle.filter( ({ record }) => record.recordType === 'agent.runtime.tool_confirmation.approved', ); assert( waiting.length === 1 && observed.length === 1 && required.length === 1 && approved.length === 1, 'confirmed-action-lifecycle-count-invalid', ); const tool = required[0].record.tool; const agentId = required[0].record.agentId; const runId = required[0].record.runId; const actionFingerprint = required[0].record.actionFingerprint; assert( isNonEmptyString(tool) && isNonEmptyString(agentId) && isNonEmptyString(runId) && isNonEmptyString(actionFingerprint) && [waiting[0], observed[0], approved[0]].every( ({ record }) => record.agentId === agentId && record.runId === runId, ) && waiting[0].record.tool === tool && observed[0].record.tool === tool && approved[0].record.tool === tool && waiting[0].record.actionFingerprint === actionFingerprint && approved[0].record.actionFingerprint === actionFingerprint && approved[0].record.confirmedRunId === runId && canonicalAuditInputSummary(required[0].record.inputSummary) === canonicalAuditInputSummary(approved[0].record.inputSummary), 'confirmed-action-lifecycle-identity-invalid', ); assert( waiting[0].index < required[0].index && required[0].index < approved[0].index && approved[0].index < observed[0].index, 'confirmed-action-lifecycle-order-invalid', ); } return state.confirmedActionIds.size; } export function validateMainRunActionReceipts( records, mainTask, historyExecution, imageInspectExecution, commandOutputReadExecution, failedCommandActionId, gitCommitExecution, ) { const receiptRecords = records.filter( (record) => record.recordType === 'agent.runtime.action_receipt', ); assertNoPersistedImagePayload('action-receipt', receiptRecords); const terminalObservations = records.filter( (record) => record.recordType === 'agent.runtime.tool_observation' && record.agentId === mainAgentId && record.runId === state.initialRunId && record.status !== 'waiting-for-confirmation' && isNonEmptyString(record.actionId), ); const mainActionIds = new Set( terminalObservations.map((record) => record.actionId), ); const mainRunReceipts = receiptRecords.filter((record) => mainActionIds.has(record.actionId), ); const declaredMainRunReceipts = receiptRecords.filter( (record) => record.runId === state.initialRunId, ); assert(mainActionIds.size > 0, 'main-run-terminal-actions-missing'); assert( mainRunReceipts.length === mainActionIds.size && declaredMainRunReceipts.length === mainRunReceipts.length, 'main-run-action-receipt-count-invalid', ); assert( mainRunReceipts.every( (record) => record.agentId === mainAgentId && record.taskId === mainTask.taskId && record.sessionId === state.initialSessionId && record.runId === state.initialRunId && /^action-[0-9a-f]{24}$/u.test(record.actionId) && /^[0-9a-f]{64}$/u.test(record.actionFingerprint) && isNonEmptyString(record.tool) && isNonEmptyString(record.executionMode) && isNonEmptyString(record.status) && Object.hasOwn(record, 'inputSummary') && (record.inputSummary == null || isNonEmptyString(record.inputSummary)) && isNonEmptyString(record.summary) && Object.hasOwn(record, 'safeDetail') && (record.safeDetail == null || typeof record.safeDetail === 'string') && typeof record.detailUnavailable === 'boolean' && Number.isSafeInteger(record.updatedAt), ), 'main-run-action-receipt-identity-invalid', ); const duplicateIdentityCount = duplicateCount( mainRunReceipts.map( (record) => `${record.actionId}\0${record.actionFingerprint}`, ), ); assert( duplicateIdentityCount === 0 && duplicateCount(mainRunReceipts.map((record) => record.actionId)) === 0, 'main-run-action-receipt-duplicate', ); for (const observation of terminalObservations) { const matches = mainRunReceipts.filter( (record) => record.actionId === observation.actionId, ); const fingerprints = new Set( records .filter( (record) => record.agentId === mainAgentId && record.runId === state.initialRunId && record.actionId === observation.actionId && isNonEmptyString(record.actionFingerprint), ) .map((record) => record.actionFingerprint), ); assert( matches.length === 1 && matches[0].tool === observation.tool && matches[0].status === observation.status && fingerprints.size === 1 && fingerprints.has(matches[0].actionFingerprint), 'terminal-action-receipt-mismatch', ); } const requiredTools = new Set([ 'project.patchset', 'git.inspect', 'image.inspect', 'command.output_read', 'agent.action_history', 'project.git_commit', ]); const coveredTools = new Set(mainRunReceipts.map((record) => record.tool)); assert( [...requiredTools].every((tool) => coveredTools.has(tool)), 'required-action-receipt-tools-missing', ); const historyReceipts = mainRunReceipts.filter( (record) => record.actionId === historyExecution.actionId && record.actionFingerprint === historyExecution.actionFingerprint && record.tool === 'agent.action_history' && record.status === 'ok', ); assert(historyReceipts.length === 1, 'action-history-receipt-count-invalid'); const imageInspectReceipts = mainRunReceipts.filter( (record) => record.actionId === imageInspectExecution.actionId && record.actionFingerprint === imageInspectExecution.actionFingerprint && record.tool === 'image.inspect' && record.executionMode === imageInspectExecution.mode && record.status === 'ok' && record.detailUnavailable === false && isNonEmptyString(record.safeDetail), ); assert( imageInspectReceipts.length === 1, 'image-inspect-receipt-count-invalid', ); let imageInspectSafeDetail; try { imageInspectSafeDetail = JSON.parse(imageInspectReceipts[0].safeDetail); } catch (error) { throw codedError('image-inspect-receipt-detail-invalid', error); } const commandOutputReadReceipts = mainRunReceipts.filter( (record) => record.tool === 'command.output_read' && record.status === 'ok' && record.detailUnavailable === false && isNonEmptyString(record.safeDetail), ); assert( commandOutputReadReceipts.some( (record) => record.actionId === commandOutputReadExecution.actionId && record.actionFingerprint === commandOutputReadExecution.actionFingerprint, ), 'command-output-read-receipt-missing', ); const commandOutputReadSafeDetails = commandOutputReadReceipts.map( (record) => { let detail; try { detail = JSON.parse(record.safeDetail); } catch (error) { throw codedError('command-output-read-receipt-detail-invalid', error); } assert( detail.sourceActionId === failedCommandActionId && isNonEmptyString(detail.sourceRunId) && /^[0-9a-f]{64}$/u.test(detail.sourceActionFingerprint) && isNonEmptyString(detail.outputRef) && /^[0-9a-f]{64}$/u.test(detail.outputSha256) && Number.isSafeInteger(detail.startLine) && detail.startLine >= 1 && Number.isSafeInteger(detail.totalLines) && !Object.hasOwn(detail, 'lines'), 'command-output-read-receipt-safe-detail-invalid', ); return detail; }, ); const gitCommitReceipts = mainRunReceipts.filter( (record) => record.actionId === gitCommitExecution.actionId && record.actionFingerprint === gitCommitExecution.actionFingerprint && record.tool === 'project.git_commit' && record.executionMode === 'confirmation' && record.status === 'ok' && record.detailUnavailable === false && isNonEmptyString(record.safeDetail), ); assert(gitCommitReceipts.length === 1, 'git-commit-receipt-count-invalid'); let gitCommitSafeDetail; try { gitCommitSafeDetail = JSON.parse(gitCommitReceipts[0].safeDetail); } catch (error) { throw codedError('git-commit-receipt-detail-invalid', error); } assert( hasExactKeys(gitCommitSafeDetail, [ 'branch', 'commitHead', 'messageSha256', 'parentHead', 'pathCount', 'paths', 'remainingChangedCount', ]) && matchesGitObjectId(gitCommitSafeDetail.parentHead) && matchesGitObjectId(gitCommitSafeDetail.commitHead) && /^[0-9a-f]{64}$/u.test(gitCommitSafeDetail.messageSha256) && isNonEmptyString(gitCommitSafeDetail.branch) && gitCommitSafeDetail.pathCount === 2 && pathListsEqual(gitCommitSafeDetail.paths, [ 'game/index.html', patchsetCreatedPath, ]) && gitCommitSafeDetail.remainingChangedCount === 1, 'git-commit-receipt-safe-detail-invalid', ); const serializedReceipts = Buffer.from( receiptRecords.map((record) => JSON.stringify(record)).join('\n'), ); const secretLeakCount = countExactSecrets(serializedReceipts, state.secrets); const lureLeakCount = countExactSecrets(serializedReceipts, state.lures); const commandOutputMarkerLeakCount = countExactSecrets(serializedReceipts, [ commandRootErrorMarker, ]); assert(secretLeakCount === 0, 'action-receipt-secret-leak-detected'); assert(lureLeakCount === 0, 'action-receipt-lure-leak-detected'); assert( commandOutputMarkerLeakCount === 0, 'action-receipt-command-output-marker-leak-detected', ); return { receiptCount: receiptRecords.length, mainRunReceiptCount: mainRunReceipts.length, requiredToolCount: requiredTools.size, duplicateIdentityCount, secretLeakCount, lureLeakCount, actionHistoryReceiptIndex: records.indexOf(historyReceipts[0]), imageInspectReceiptCount: imageInspectReceipts.length, imageInspectReceiptIndex: records.indexOf(imageInspectReceipts[0]), imageInspectSafeDetail, commandOutputReadReceiptCount: commandOutputReadReceipts.length, commandOutputReadSafeDetails, commandOutputMarkerLeakCount, gitCommitReceiptCount: gitCommitReceipts.length, gitCommitReceiptIndex: records.indexOf(gitCommitReceipts[0]), gitCommitSafeDetail, }; } export function validatePreviewScreenshotPaths(screenshots, code) { assert( Array.isArray(screenshots) && screenshots.length === 2 && screenshots.every( (entry) => isNonEmptyString(entry) && !path.isAbsolute(entry) && !entry.includes('\\'), ) && screenshots[0].endsWith('/desktop.png') && screenshots[1].endsWith('/mobile.png') && new Set(screenshots).size === screenshots.length, code, ); return screenshots; } export function validateImageInspectAudit( record, expectedImages, receiptDetail, ) { assert( hasExactKeys(record, [ 'agentId', 'conclusionChars', 'images', 'recordType', 'responseId', 'runId', 'schemaVersion', 'updatedAt', ]) && isNonEmptyString(record.schemaVersion) && Number.isSafeInteger(record.updatedAt) && isNonEmptyString(record.responseId) && Number.isSafeInteger(record.conclusionChars) && record.conclusionChars > 0 && record.conclusionChars <= 7_000, 'image-inspect-dedicated-audit-fields-invalid', ); validateImageInspectMetadata( record.images, expectedImages, 'image-inspect-dedicated-audit-images-invalid', ); assert( hasExactKeys(receiptDetail, ['conclusionChars', 'images', 'responseId']) && receiptDetail.responseId === record.responseId && receiptDetail.conclusionChars === record.conclusionChars, 'image-inspect-receipt-fields-invalid', ); validateImageInspectMetadata( receiptDetail.images, expectedImages, 'image-inspect-receipt-images-invalid', ); } export function validateImageInspectMetadata(actual, expected, code) { assert( Array.isArray(actual) && actual.length === expected.length && actual.every( (image, index) => hasExactKeys(image, ['bytes', 'path', 'sha256']) && image.path === expected[index].path && image.sha256 === expected[index].sha256 && image.bytes === expected[index].bytes && /^[0-9a-f]{64}$/u.test(image.sha256) && Number.isSafeInteger(image.bytes) && image.bytes > 0, ), code, ); } export function hasExactKeys(value, expectedKeys) { if (!value || typeof value !== 'object' || Array.isArray(value)) return false; const actual = Object.keys(value).sort(); const expected = [...expectedKeys].sort(); return ( actual.length === expected.length && actual.every((key, index) => key === expected[index]) ); } export function matchesGitObjectId(value) { return ( typeof value === 'string' && [40, 64].includes(value.length) && /^[0-9a-f]+$/u.test(value) ); } export function pathListsEqual(actual, expected) { if ( !Array.isArray(actual) || actual.some((entry) => typeof entry !== 'string') ) { return false; } const left = [...actual].sort(); const right = [...expected].sort(); return ( left.length === right.length && left.every((entry, index) => entry === right[index]) ); } export function assertNoPersistedImagePayload(surface, records) { const serialized = records.map((record) => JSON.stringify(record)).join('\n'); assert( !/data:image(?:\/|%2f)/iu.test(serialized), `${surface}-data-image-payload-leak`, ); assert( !/(?:;|%3b)base64(?:,|%2c)[a-z0-9+/=\r\n]{128,}/iu.test(serialized) && !/[a-z0-9+/]{512,}={0,2}/iu.test(serialized), `${surface}-base64-image-payload-leak`, ); } export function validateActionHistoryObservations( events, contextObservations, historyExecution, patchsetExecution, mainTask, ) { const eventObservations = events.filter( (event) => event.agentId === mainAgentId && event.runId === state.initialRunId && event.actionId === historyExecution.actionId && event.eventType === 'observation' && String(event.summary ?? '').startsWith('agent.action_history:ok') && isNonEmptyString(event.detail), ); const bundledObservations = (contextObservations ?? []).filter( (observation) => observation?.tool === 'agent.action_history' && observation?.status === 'ok' && isNonEmptyString(observation.detail) && observation.detail.includes(patchsetExecution.actionId), ); assert( eventObservations.length === 1 && bundledObservations.length === 1, 'action-history-observation-count-invalid', ); const payloads = [eventObservations[0], bundledObservations[0]].map( (observation) => { let payload; try { payload = JSON.parse(observation.detail); } catch (error) { throw codedError('action-history-observation-json-invalid', error); } const actions = Array.isArray(payload.actions) ? payload.actions : []; const patchsetActions = actions.filter( (action) => action.actionId === patchsetExecution.actionId, ); assert( payload.runId === state.initialRunId && payload.count === actions.length && payload.truncated === false && actions.length === 1 && patchsetActions.length === 1 && patchsetActions[0].agentId === mainAgentId && patchsetActions[0].taskId === mainTask.taskId && patchsetActions[0].sessionId === state.initialSessionId && patchsetActions[0].actionFingerprint === patchsetExecution.actionFingerprint && patchsetActions[0].runId === state.initialRunId && patchsetActions[0].tool === 'project.patchset' && patchsetActions[0].status === 'ok' && !actions.some( (action) => action.actionId === historyExecution.actionId || action.tool === 'agent.action_history', ), 'action-history-observation-evidence-invalid', ); return payload; }, ); const actions = payloads[0].actions; return { resultCount: actions.length, recursiveResultCount: actions.filter( (action) => action.tool === 'agent.action_history', ).length, }; } export function validateToolActionReplays(records) { const attemptsByActionId = new Map(); for (const record of records) { if ( ![ 'agent.runtime.tool_action.executing', 'agent.runtime.tool_confirmation_required', 'agent.runtime.action_receipt', ].includes(record.recordType) ) { continue; } assert( isNonEmptyString(record.agentId) && isNonEmptyString(record.runId) && isNonEmptyString(record.actionId) && isNonEmptyString(record.actionFingerprint) && isNonEmptyString(record.tool), 'tool-action-replay-identity-missing', ); const attempt = { recordType: record.recordType, agentId: record.agentId, runId: record.runId, actionId: record.actionId, actionFingerprint: record.actionFingerprint, tool: record.tool, status: record.status ?? null, rawInputSummary: record.inputSummary ?? null, inputSummary: canonicalAuditInputSummary(record.inputSummary), }; const existing = attemptsByActionId.get(attempt.actionId); if (existing) { const sameIdentity = existing.agentId === attempt.agentId && existing.runId === attempt.runId && existing.actionFingerprint === attempt.actionFingerprint && existing.tool === attempt.tool && existing.inputSummary === attempt.inputSummary; assert( sameIdentity || goalStaleActionReceiptMatchesOriginalAction(existing, attempt), 'tool-action-replay-identity-conflict', ); } else { attemptsByActionId.set(attempt.actionId, attempt); } } const sideEffectsByIdentity = new Map(); const observationsByIdentity = new Map(); for (const attempt of attemptsByActionId.values()) { const terminalReceipt = records.find( (record) => record.recordType === 'agent.runtime.action_receipt' && record.agentId === attempt.agentId && record.runId === attempt.runId && record.actionId === attempt.actionId && record.tool === attempt.tool, ); const commandTerminalStatus = attempt.tool === 'command.exec' ? (terminalReceipt?.status ?? '[missing-terminal-status]') : ''; const identity = `${attempt.agentId}\0${attempt.runId}\0${attempt.tool}\0${attempt.actionFingerprint}\0${commandTerminalStatus}`; const idempotentObservation = idempotentObservationTools.has(attempt.tool); const sideEffectOccurred = terminalReceipt?.status === 'ok' || (attempt.tool === 'command.exec' && terminalReceipt?.status === 'command-failed'); if (!idempotentObservation && !sideEffectOccurred) continue; const target = idempotentObservation ? observationsByIdentity : sideEffectsByIdentity; const actionIds = target.get(identity) ?? new Set(); actionIds.add(attempt.actionId); target.set(identity, actionIds); } const sideEffectReplayCount = replayCount(sideEffectsByIdentity); assert(sideEffectReplayCount === 0, 'side-effect-action-replay-detected'); return { sideEffectActionCount: [...sideEffectsByIdentity.values()].reduce( (count, actionIds) => count + actionIds.size, 0, ), sideEffectReplayCount, idempotentReplayActionCount: replayCount(observationsByIdentity), actionReceiptReplayRecordCount: records.filter( (record) => record.recordType === 'agent.runtime.action_receipt', ).length, }; } export function canonicalAuditInputSummary(summary) { if (summary == null || summary === '') return '[empty]'; assert(typeof summary === 'string', 'audit-input-summary-invalid'); const segments = summary .split(' · ') .map((segment) => segment.trim()) .filter(Boolean) .map((segment) => { const separator = segment.indexOf('='); if (separator <= 0) return segment.replace(/\s+/gu, ' '); const key = segment.slice(0, separator).trim(); let value = segment.slice(separator + 1).trim(); if (key === 'path' && value !== '[absolute path rejected]') { value = path.posix .normalize(value.replaceAll('\\', '/')) .replace(/^\.\//u, ''); } return `${key}=${value}`; }) .sort(); assert(segments.length > 0, 'audit-input-summary-empty'); return segments.join(' · '); } export function replayCount(actionsByIdentity) { let count = 0; for (const actionIds of actionsByIdentity.values()) { if (actionIds.size > 1) count += actionIds.size - 1; } return count; } export function findSuccessfulToolExecution( records, tool, runId, matches = () => true, ) { const indexed = records.map((record, index) => ({ record, index })); const sameAction = (record, candidate) => record.runId === runId && record.agentId === mainAgentId && record.tool === tool && record.actionId === candidate.actionId && record.actionFingerprint === candidate.actionFingerprint; for (const candidate of indexed) { const observed = candidate.record; if ( observed.recordType !== 'agent.runtime.tool_action.observed' || observed.runId !== runId || observed.agentId !== mainAgentId || observed.tool !== tool || observed.executionMode !== 'auto' || observed.observationStatus !== 'ok' || !isNonEmptyString(observed.actionId) || !isNonEmptyString(observed.actionFingerprint) ) { continue; } const executing = findLastIndexedRecord( indexed, candidate.index, ({ record }) => record.recordType === 'agent.runtime.tool_action.executing' && record.executionMode === 'auto' && sameAction(record, observed), ); if (!executing) continue; const execution = { tool, mode: 'auto', runId, actionId: observed.actionId, actionFingerprint: observed.actionFingerprint, inputSummary: executing.record.inputSummary ?? null, startIndex: executing.index, resultIndex: candidate.index, completionIndex: -1, }; if (!matches(execution)) continue; const completion = indexed.find( ({ record, index }) => index > candidate.index && record.recordType === 'agent.runtime.tool_observation' && record.runId === runId && record.agentId === mainAgentId && record.tool === tool && record.status === 'ok' && (!record.actionId || record.actionId === observed.actionId), ); if (completion) { execution.completionIndex = completion.index; return execution; } } for (const candidate of indexed) { const observation = candidate.record; if ( observation.recordType !== 'agent.runtime.tool_observation' || observation.runId !== runId || observation.agentId !== mainAgentId || observation.tool !== tool || observation.status !== 'ok' || observation.decision !== 'approved' || !isNonEmptyString(observation.actionId) ) { continue; } const approval = findLastIndexedRecord( indexed, candidate.index, ({ record }) => record.recordType === 'agent.runtime.tool_confirmation.approved' && record.runId === runId && record.confirmedRunId === runId && record.agentId === mainAgentId && record.tool === tool && record.actionId === observation.actionId && isNonEmptyString(record.actionFingerprint), ); if (!approval) continue; const execution = { tool, mode: 'confirmation', runId, actionId: observation.actionId, actionFingerprint: approval.record.actionFingerprint, inputSummary: approval.record.inputSummary ?? null, startIndex: approval.index, resultIndex: candidate.index, completionIndex: candidate.index, }; if (matches(execution)) return execution; } return null; } export function requireSuccessfulToolExecution( records, tool, runId, matches, code = `required-tool-evidence-missing:${tool}`, ) { const execution = findSuccessfulToolExecution(records, tool, runId, matches); assert(Boolean(execution), code); return execution; } export function requireExecutionRecord(records, execution, matches, code) { for ( let index = execution.startIndex + 1; index < execution.resultIndex; index += 1 ) { if (matches(records[index])) return records[index]; } throw codedError(code); } export function validatePatchsetAudit( records, execution, checkpointId, { initialGameSha256, expectedGameSha256, expectedCreatedSha256 }, ) { const audits = records.filter( (record) => record.agentId === mainAgentId && record.runId === state.initialRunId && record.actionId === execution.actionId && String(record.recordType ?? '').startsWith( 'agent.runtime.project.patchset', ), ); const prepared = audits.filter( (record) => patchsetAuditPhase(record) === 'prepared', ); const completed = audits.filter( (record) => patchsetAuditPhase(record) === 'completed', ); const failed = audits.filter((record) => ['failed', 'needs-reconciliation'].includes(patchsetAuditPhase(record)), ); assert(prepared.length === 1, 'patchset-prepared-audit-count-invalid'); assert(completed.length === 1, 'patchset-completed-audit-count-invalid'); assert(failed.length === 0, 'patchset-failed-audit-present'); assert( [prepared[0], completed[0]].every( (record) => record.checkpointId === checkpointId && record.actionFingerprint === execution.actionFingerprint, ), 'patchset-audit-identity-invalid', ); assert( patchsetAuditChangeCount(prepared[0]) === 2, 'patchset-prepared-change-count-invalid', ); assert( Number.isSafeInteger(completed[0].revisionBefore) && completed[0].revisionAfter === completed[0].revisionBefore + 1, 'patchset-revision-delta-invalid', ); const changes = patchsetAuditChanges(completed[0]); assert(changes.length === 2, 'patchset-completed-change-count-invalid'); const byPath = new Map(changes.map((change) => [change.path, change])); assert(byPath.size === changes.length, 'patchset-audit-duplicate-path'); const update = byPath.get('game/index.html'); const created = byPath.get(patchsetCreatedPath); assert( update?.operation === 'update' && patchsetAuditSha256(update, 'before') === initialGameSha256 && patchsetAuditSha256(update, 'after') === expectedGameSha256 && patchsetAuditBytes(update, 'before') === Buffer.byteLength(seededGameHtml()) && patchsetAuditBytes(update, 'after') === Buffer.byteLength( seededGameHtml().replace('REAL_E2E_TARGET:before', patchedText), ), 'patchset-update-audit-invalid', ); const createdBeforeSha256 = patchsetAuditSha256(created, 'before'); const createdBeforeBytes = patchsetAuditBytes(created, 'before'); assert( created?.operation === 'create' && [null, '', '-'].includes(createdBeforeSha256) && [null, 0].includes(createdBeforeBytes) && patchsetAuditSha256(created, 'after') === expectedCreatedSha256 && patchsetAuditBytes(created, 'after') === Buffer.byteLength(patchsetCreatedContent), 'patchset-create-audit-invalid', ); const serializedAudits = JSON.stringify(audits); assert( [ 'REAL_E2E_TARGET:before', patchedText, patchsetCreatedMarker, visibleText, ].every((content) => !serializedAudits.includes(content)), 'patchset-audit-source-content-leak', ); return { preparedCount: prepared.length, completedCount: completed.length, changeCount: changes.length, revisionDelta: completed[0].revisionAfter - completed[0].revisionBefore, }; } export function patchsetAuditPhase(record) { const recordType = String(record.recordType ?? '').toLowerCase(); for (const phase of ['prepared', 'completed', 'failed']) { if (recordType.endsWith(`.${phase}`)) return phase; } const phase = String(record.phase ?? record.status ?? '').toLowerCase(); if (phase === 'needs-reconciliation') return phase; if (['prepared', 'completed', 'failed'].includes(phase)) return phase; return ''; } export function patchsetAuditChanges(record) { for (const key of ['changes', 'files', 'entries']) { if (Array.isArray(record?.[key])) return record[key]; } return []; } export function patchsetAuditChangeCount(record) { for (const key of ['changeCount', 'fileCount', 'entryCount']) { if (Number.isSafeInteger(record?.[key])) return record[key]; } return patchsetAuditChanges(record).length; } export function patchsetAuditSha256(change, side) { if (!change || typeof change !== 'object') return null; const keys = side === 'before' ? ['beforeSha256', 'previousSha256', 'oldSha256', 'checkpointSha256'] : ['afterSha256', 'currentSha256', 'newSha256']; for (const key of keys) { if (Object.hasOwn(change, key)) return change[key]; } return null; } export function patchsetAuditBytes(change, side) { if (!change || typeof change !== 'object') return null; const keys = side === 'before' ? ['beforeBytes', 'previousBytes', 'oldBytes'] : ['afterBytes', 'currentBytes', 'newBytes', 'bytes', 'byteCount']; for (const key of keys) { if (Object.hasOwn(change, key)) return change[key]; } return null; } export function validatePatchsetContentDiff( observations, checkpointId, { initialGameSha256, expectedGameSha256, expectedCreatedSha256 }, ) { assert(Array.isArray(observations), 'context-observations-missing'); const observation = observations.find( (candidate) => candidate.tool === 'project.diff' && candidate.status === 'ok' && isNonEmptyString(candidate.detail) && `${candidate.summary ?? ''}\n${candidate.detail}`.includes( checkpointId, ) && String(candidate.detail).includes( 'diff --git a/game/index.html b/game/index.html', ) && String(candidate.detail).includes( `diff --git a/${patchsetCreatedPath} b/${patchsetCreatedPath}`, ), ); assert(Boolean(observation), 'patchset-content-diff-observation-missing'); const detail = observation.detail; const sections = [...detail.matchAll(/(?:^|\n)(diff --git a\/[^\n]+)/gu)]; assert( sections.length === 2 && detail.includes('contentFileCount: 2') && detail.includes('contentTruncated: false'), 'patchset-content-diff-file-count-invalid', ); const update = contentDiffSection(detail, 'game/index.html'); const created = contentDiffSection(detail, patchsetCreatedPath); assert( update.includes('status: changed') && update.includes(`checkpoint-sha256: ${initialGameSha256}`) && update.includes(`current-sha256: ${expectedGameSha256}`) && update.includes('--- a/game/index.html') && update.includes('+++ b/game/index.html') && /(?:^|\n)@@ [^\n]+ @@/u.test(update) && update.includes( '-
REAL_E2E_TARGET:before
', ) && update.includes(`+${patchedText}
`), 'patchset-update-content-hunk-invalid', ); assert( created.includes('status: added') && created.includes('checkpoint-sha256: -') && created.includes(`current-sha256: ${expectedCreatedSha256}`) && created.includes('--- /dev/null') && created.includes(`+++ b/${patchsetCreatedPath}`) && /(?:^|\n)@@ [^\n]+ @@/u.test(created) && created.includes(`+${patchsetCreatedMarker}`), 'patchset-create-content-hunk-invalid', ); return { fileCount: sections.length }; } export async function validateGitCommitEvidence( records, execution, receiptDetail, projectRevision, { expectedGameHtml, expectedCreatedContent }, ) { const audits = records.filter( (record) => record.recordType === 'agent.runtime.project.git_commit' && record.agentId === mainAgentId && record.runId === state.initialRunId && record.actionId === execution.actionId && record.actionFingerprint === execution.actionFingerprint, ); assert(audits.length === 1, 'git-commit-dedicated-audit-count-invalid'); const audit = audits[0]; const expectedPaths = ['game/index.html', patchsetCreatedPath]; assert( hasExactKeys(audit, [ 'actionFingerprint', 'actionId', 'agentId', 'branch', 'commitHead', 'messageSha256', 'parentHead', 'pathCount', 'paths', 'recordType', 'remainingChangedCount', 'revision', 'runId', 'schemaVersion', 'updatedAt', ]) && isNonEmptyString(audit.schemaVersion) && Number.isSafeInteger(audit.updatedAt) && audit.revision === projectRevision && matchesGitObjectId(audit.parentHead) && matchesGitObjectId(audit.commitHead) && audit.parentHead !== audit.commitHead && isNonEmptyString(audit.branch) && audit.pathCount === expectedPaths.length && pathListsEqual(audit.paths, expectedPaths) && /^[0-9a-f]{64}$/u.test(audit.messageSha256) && audit.messageSha256 === auditInputValue(execution.inputSummary, 'messageSha256') && audit.remainingChangedCount === 1, 'git-commit-dedicated-audit-invalid', ); assert( receiptDetail.parentHead === audit.parentHead && receiptDetail.commitHead === audit.commitHead && receiptDetail.branch === audit.branch && receiptDetail.pathCount === audit.pathCount && pathListsEqual(receiptDetail.paths, audit.paths) && receiptDetail.messageSha256 === audit.messageSha256 && receiptDetail.remainingChangedCount === audit.remainingChangedCount, 'git-commit-audit-receipt-mismatch', ); const git = (args) => runProcess( 'git', ['-c', 'core.pager=cat', '-c', 'color.ui=false', ...args], { cwd: state.projectRoot, timeoutMs: 30_000 }, ); const headResult = await git(['rev-parse', 'HEAD']); const parentResult = await git(['rev-parse', 'HEAD^']); const branchResult = await git([ 'symbolic-ref', '--quiet', '--short', 'HEAD', ]); const commitCountResult = await git(['rev-list', '--count', 'HEAD']); const commitObjectResult = await git(['cat-file', 'commit', 'HEAD']); const committedPathsResult = await git([ 'diff-tree', '--no-commit-id', '--name-only', '-r', '-z', 'HEAD', ]); const stagedResult = await git(['diff', '--cached', '--name-only']); const selectedStatusResult = await git([ 'status', '--porcelain=v1', '-z', '--', ...expectedPaths, ]); const committedGameResult = await git(['show', `HEAD:${expectedPaths[0]}`]); const committedCreatedResult = await git([ 'show', `HEAD:${expectedPaths[1]}`, ]); const head = headResult.stdout.trim(); const parent = parentResult.stdout.trim(); const branch = branchResult.stdout.trim(); const commitMessageSeparator = commitObjectResult.stdout.indexOf('\n\n'); assert(commitMessageSeparator > 0, 'git-commit-object-message-missing'); const rawCommitMessage = commitObjectResult.stdout.slice( commitMessageSeparator + 2, ); const rawCommitMessageBytes = Buffer.from(rawCommitMessage, 'utf8'); const messageHashCandidates = [ createHash('sha256').update(rawCommitMessageBytes).digest('hex'), ]; if (rawCommitMessageBytes.at(-1) === 0x0a) { messageHashCandidates.push( createHash('sha256') .update(rawCommitMessageBytes.subarray(0, -1)) .digest('hex'), ); } const commitMessage = rawCommitMessage.endsWith('\n') ? rawCommitMessage.slice(0, -1) : rawCommitMessage; const committedPaths = committedPathsResult.stdout .split('\0') .filter(Boolean); assert( head === audit.commitHead && parent === audit.parentHead && branch === audit.branch && Number(commitCountResult.stdout.trim()) === 2, 'git-commit-head-parent-branch-invalid', ); assert( isNonEmptyString(commitMessage) && messageHashCandidates.includes(audit.messageSha256) && commitMessage.split(/\r?\n/u)[0] === auditInputValue(execution.inputSummary, 'title') && state.lures.every((lure) => !commitMessage.includes(lure)), 'git-commit-message-invalid', ); assert( pathListsEqual(committedPaths, expectedPaths) && stagedResult.stdout === '' && selectedStatusResult.stdout === '' && committedGameResult.stdout === expectedGameHtml && committedCreatedResult.stdout === expectedCreatedContent, 'git-commit-tree-or-index-invalid', ); const gitLogsRoot = path.join(state.projectRoot, '.git/logs'); const branchLogPath = path.resolve( gitLogsRoot, 'refs/heads', ...branch.split('/'), ); assert( isPathInside(gitLogsRoot, branchLogPath), 'git-commit-branch-reflog-path-invalid', ); const [headLog, branchLog] = await Promise.all([ fs.readFile(path.join(gitLogsRoot, 'HEAD'), 'utf8'), fs.readFile(branchLogPath, 'utf8'), ]); const lastReflogLine = (content) => content.split(/\r?\n/u).filter(Boolean).at(-1); const reflogMatches = (line) => isNonEmptyString(line) && line.startsWith(`${parent} ${head} `) && line.endsWith(`\t${gitCommitReflogMessage}`); assert( reflogMatches(lastReflogLine(headLog)) && reflogMatches(lastReflogLine(branchLog)), 'git-commit-reflog-invalid', ); return { commitHead: head, pathCount: committedPaths.length, auditCount: audits.length, parentMatched: true, treeMatched: true, reflogMatched: true, }; } export function validateGitInspectEvents( events, contextObservations, { initialActionId, changedActionId, postCommitActionId, commitHead }, ) { const observations = events.filter( (event) => event.agentId === mainAgentId && event.runId === state.initialRunId && event.eventType === 'observation' && String(event.summary ?? '').startsWith('git.inspect:ok') && isNonEmptyString(event.detail), ); assert(observations.length >= 3, 'git-inspect-observation-count-invalid'); const initial = observations.find( (observation) => observation.actionId === initialActionId, ); const changed = observations.find( (observation) => observation.actionId === changedActionId, ); const postCommit = observations.find( (observation) => observation.actionId === postCommitActionId, ); assert(Boolean(initial), 'initial-git-inspect-observation-missing'); assert(Boolean(changed), 'changed-git-inspect-observation-missing'); assert(Boolean(postCommit), 'post-commit-git-inspect-observation-missing'); const initialDetail = String(initial.detail); const changedDetail = String(changed.detail); const postCommitDetail = String(postCommit.detail); assert( initialDetail.includes('\nstaged: 0\n') && initialDetail.includes('\nunstaged: 0\n') && initialDetail.includes('\nuntracked: 1\n') && initialDetail.includes('\ngitContentFileCount: 1\n') && initialDetail.includes('\ngitContentTruncated: false\n') && initialDetail.includes(`- ${sentinelFileName}`) && !initialDetail.includes('diff --git ') && !initialDetail.includes(patchsetCreatedPath), 'initial-git-inspect-observation-invalid', ); const fileCount = Number( /^gitContentFileCount:\s*(\d+)$/mu.exec(changedDetail)?.[1] ?? Number.NaN, ); assert( Number.isSafeInteger(fileCount) && fileCount === 3 && changedDetail.includes('\nstaged: 0\n') && changedDetail.includes('\nunstaged: 1\n') && changedDetail.includes('\nuntracked: 2\n') && changedDetail.includes('gitContentTruncated: false') && changedDetail.includes('## unstaged files') && changedDetail.includes('- game/index.html') && changedDetail.includes('## untracked files') && changedDetail.includes(`- ${patchsetCreatedPath}`) && changedDetail.includes(`- ${sentinelFileName}`), 'changed-git-inspect-observation-invalid', ); assert( postCommitDetail.includes(`head: ${commitHead}\n`) && postCommitDetail.includes('\nstaged: 0\n') && postCommitDetail.includes('\nunstaged: 0\n') && postCommitDetail.includes('\nuntracked: 1\n') && postCommitDetail.includes('\ngitContentFileCount: 1\n') && postCommitDetail.includes('\ngitContentTruncated: false\n') && /^commitSnapshotFingerprint:\s*[0-9a-f]{64}$/mu.test(postCommitDetail) && !postCommitDetail.includes('diff --git ') && !postCommitDetail.includes(patchsetCreatedPath) && !postCommitDetail.includes('- game/index.html') && postCommitDetail.includes(`- ${sentinelFileName}`), 'post-commit-git-inspect-observation-invalid', ); for (const forbidden of [ '.env', configFileName, '.agent/', gitSensitivePath, ...state.lures, ]) { assert( !initialDetail.includes(forbidden) && !changedDetail.includes(forbidden) && !postCommitDetail.includes(forbidden), 'git-inspect-sensitive-observation-leak', ); } const protectedObservation = [...contextObservations].find( (observation) => observation?.tool === 'git.inspect' && observation?.status === 'ok' && isNonEmptyString(observation.detail) && observation.detail.includes( 'diff --git a/game/index.html b/game/index.html', ), ); assert( protectedObservation?.detail.includes( 'diff --git a/game/index.html b/game/index.html', ) && protectedObservation.detail.includes(`- ${patchsetCreatedPath}`) && protectedObservation.detail.includes( `+${patchedText}
`, ) && protectedObservation.detail.includes('gitContentTruncated: false'), 'git-inspect-context-bundle-evidence-missing', ); for (const forbidden of [ '.env', configFileName, '.agent/', gitSensitivePath, ...state.lures, ]) { assert( !protectedObservation.detail.includes(forbidden), 'git-inspect-context-bundle-sensitive-leak', ); } return { changedFileCount: fileCount, postCommitSelectedPathsClean: true, }; } export function contentDiffSection(detail, relativePath) { const header = `diff --git a/${relativePath} b/${relativePath}`; const start = detail.indexOf(header); assert(start >= 0, `content-diff-section-missing:${relativePath}`); const next = detail.indexOf('\ndiff --git ', start + header.length); return detail.slice(start, next < 0 ? detail.length : next); } export function findLastIndexedRecord(indexed, beforeIndex, matches) { for (let index = beforeIndex - 1; index >= 0; index -= 1) { if (matches(indexed[index])) return indexed[index]; } return null; } export function auditInputValue(summary, key) { if (typeof summary !== 'string') return null; for (const segment of summary.split(' · ')) { const separator = segment.indexOf('='); if (separator > 0 && segment.slice(0, separator) === key) { return segment.slice(separator + 1); } } return null; } export function auditPatchsetPathsMatch(summary, expectedPaths) { const value = auditInputValue(summary, 'paths'); if (!isNonEmptyString(value)) return false; const actual = value .split(',') .map((entry) => entry.trim()) .filter(Boolean) .sort(); const expected = [...expectedPaths].sort(); return ( actual.length === expected.length && actual.every((entry, index) => entry === expected[index]) ); } export function auditPatchsetPathsInclude(summary, expectedPaths) { const value = auditInputValue(summary, 'paths'); if (!isNonEmptyString(value)) return false; const actual = new Set( value .split(',') .map((entry) => entry.trim()) .filter(Boolean), ); return expectedPaths.every((entry) => actual.has(entry)); } export function auditPathListMatches(summary, key, expectedPaths) { const value = auditInputValue(summary, key); if (!isNonEmptyString(value)) return false; return pathListsEqual( value .split(',') .map((entry) => entry.trim()) .filter(Boolean), expectedPaths, ); } export function auditPathEquals(summary, expectedPath) { const value = auditInputValue(summary, 'path'); if (!isNonEmptyString(value) || value === '[absolute path rejected]') return false; const normalized = path.posix .normalize(value.replaceAll('\\', '/')) .replace(/^\.\//u, ''); return normalized === expectedPath; } export function isNonEmptyString(value) { return typeof value === 'string' && value.trim().length > 0; } export function isolatedJoinDeliveryTarget(delivery) { const target = delivery && Object.hasOwn(delivery, 'deliveryTarget') ? delivery.deliveryTarget : 'continuation'; assert( target === 'continuation' || target === 'parent-wake', 'isolated-join-delivery-target-invalid', ); return target; } function runtimeMessageCorrelationId(agentId, sessionId, runId) { return createHash('sha256') .update(`${agentId}\n${sessionId}\n${runId}`) .digest('hex'); } export function finalMessageId(agentId, sessionId, runId) { return `agent-finalization-${runtimeMessageCorrelationId( agentId, sessionId, runId, ).slice(0, 32)}`; } export function runtimePublicStatusMessageId( agentId, sessionId, runId, status, ) { const correlationId = runtimeMessageCorrelationId( agentId, sessionId, runId, ).slice(0, 32); const statusFingerprint = createHash('sha256') .update(status) .digest('hex') .slice(0, 16); return `runtime-public-status-${correlationId}-${statusFingerprint}`; } export function backgroundTaskMessageId(agentId, sessionId, runId, source) { const fingerprint = createHash('sha256') .update(`${agentId}\n${sessionId}\n${runId}\n${source}`) .digest('hex'); return `runtime-task-${fingerprint.slice(0, 32)}`; } export function actionReceiptIdentity(record) { return JSON.stringify([ record.agentId, record.taskId, record.sessionId, record.runId, record.actionId, record.actionFingerprint, record.tool, record.executionMode, record.status, record.inputSummary, record.summary, record.safeDetail, record.detailUnavailable, record.updatedAt, ]); } export function disposableProjectPathVariants() { return absolutePathVariants(state.projectRoot); } export function formalConfigPathVariants() { return absolutePathVariants( state.options?.configDir, state.isolatedRunner?.appDataDir, ); } export function absolutePathVariants(...values) { const variants = []; for (const value of values.filter(isNonEmptyString)) { const absolute = path.resolve(value); const forward = path.posix.normalize(absolute.replaceAll('\\', '/')); const backward = path.win32.normalize(absolute.replaceAll('/', '\\')); variants.push( value, absolute, path.normalize(absolute), forward, backward, forward.replaceAll('/', '\\'), backward.replaceAll('\\', '/'), ); } return [...new Set(variants)].filter( (value) => isNonEmptyString(value) && value.length > 1, ); } export function sumObjectValues(value) { return Object.values(value).reduce((total, count) => { assert(Number.isSafeInteger(count) && count >= 0, 'leak-count-invalid'); return total + count; }, 0); } export function countBy(values) { const counts = new Map(); for (const value of values) counts.set(value, (counts.get(value) ?? 0) + 1); return counts; } export function duplicateCount(values) { let duplicates = 0; for (const count of countBy(values).values()) { if (count > 1) duplicates += count - 1; } return duplicates; } export function actionAuditIdentity(record) { const lifecycle = record.recordType === 'agent.runtime.tool_observation' ? `:${record.status ?? 'unknown'}` : ''; return `${record.recordType}:${record.actionId}${lifecycle}`; } export function receiptAuditIdentity(record) { if (record.recordType === 'agent.runtime.action_receipt') { return `${record.recordType}:${record.agentId}:${record.runId}:${record.actionId}:${record.actionFingerprint}`; } return `${record.recordType}:${record.receiptRunId ?? record.joinRunId ?? record.delegationGroupId}`; } export function countExactSecrets(content, secrets) { const values = [...new Set(secrets.filter(isNonEmptyString))]; if (values.length === 0) return 0; const text = Buffer.isBuffer(content) ? content.toString('utf8') : String(content); const structured = parseStructuredSecretScanDocuments(text); if (structured) { let count = 0; const visit = (value) => { if (typeof value === 'string') { for (const secret of values) { count += countNonOverlappingTextOccurrences(value, secret); } return; } if (Array.isArray(value)) { for (const item of value) visit(item); return; } if (!isPlainObject(value)) return; for (const nested of Object.values(value)) visit(nested); }; for (const document of structured) visit(document); return count; } let count = 0; for (const secret of values) { const escaped = JSON.stringify(secret).slice(1, -1); const representations = [...new Set([secret, escaped])].sort( (left, right) => right.length - left.length, ); const matchedRanges = []; for (const representation of representations) { let offset = 0; while (offset <= text.length - representation.length) { const index = text.indexOf(representation, offset); if (index < 0) break; const end = index + representation.length; if ( !matchedRanges.some( ([matchedStart, matchedEnd]) => index < matchedEnd && matchedStart < end, ) ) { matchedRanges.push([index, end]); } offset = index + Math.max(1, representation.length); } } count += matchedRanges.length; } return count; } export function parseStructuredSecretScanDocuments(text) { try { return [JSON.parse(text)]; } catch { const lines = text.split(/\r?\n/u).filter((line) => line.trim().length > 0); if (lines.length < 2) return null; const documents = []; for (const line of lines) { try { documents.push(JSON.parse(line)); } catch { return null; } } return documents; } } export function countNonOverlappingTextOccurrences(text, value) { let count = 0; let offset = 0; while (offset <= text.length - value.length) { const index = text.indexOf(value, offset); if (index < 0) break; count += 1; offset = index + Math.max(1, value.length); } return count; }