Files
kdletters 2ac97b3f5e
Project CI / Repository checks (push) Successful in 3m50s
Project CI / Frontend tests (push) Successful in 4m41s
Project CI / Backend tests (push) Successful in 5m35s
Project CI / Native shell tests (push) Failing after 9m59s
退役客户端 MCP 约束与运行链路
删除客户端 MCP 配置、Runtime 工具和安全门禁

移除 MCP E2E 套件、测试夹具和测试辅助代码

清理 MCP 依赖及 Skill/Prompt 中的 MCP 约束
2026-08-26 02:32:05 +08:00

2618 lines
89 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_historyok') &&
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(
'- <p id="patch-state">REAL_E2E_TARGET:before</p>',
) &&
update.includes(`+ <p id="patch-state">${patchedText}</p>`),
'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.inspectok') &&
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(
`+ <p id="patch-state">${patchedText}</p>`,
) &&
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;
}