@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
274 lines (262 loc) • 15 kB
text/typescript
import { gateDefinitionHash } from "./gate-hash.ts";
import type { KxmRunPlanEnvelope } from "./engine-plan.ts";
import type { KxmRunState } from "./engine-fold.ts";
import { kxmCanonicalJson, type JsonValue } from "./project-config.ts";
import {
assertClosedGateObservation,
computeGateEvidenceOutcome,
gateRowContentHash,
runtimeError,
type KxmGateAttemptRow,
type KxmGateEvidenceRow,
type KxmGateObservationRow,
type KxmRunEvent,
type KxmRunEventStore,
type KxmRunRecord,
} from "./runtime-store.ts";
export function verifyKxmGateEvidence(
store: KxmRunEventStore,
run: KxmRunRecord,
envelope: KxmRunPlanEnvelope,
events: readonly KxmRunEvent[],
state: KxmRunState,
): void {
const attempts = store.gateAttemptsForRun(run.runId);
const observations = store.gateObservationsForRun(run.runId);
const evidence = store.gateEvidenceForRun(run.runId);
const counts = store.gateRowCounts(run.runId);
if (counts.attempts !== attempts.length || counts.observations !== observations.length || counts.evidence !== evidence.length) {
throw runtimeError("gate_evidence_orphan", run.runId, "gate row counts do not match loaded attempt/observation/evidence tuples");
}
const attemptById = new Map(attempts.map((row) => [row.attemptId, row]));
const observationByAttempt = new Map(observations.map((row) => [row.attemptId, row]));
const evidenceByAttempt = new Map(evidence.map((row) => [row.attemptId, row]));
const intentEvents = events.filter((event) => event.eventType === "effect.intent_recorded");
const observedEvents = events.filter((event) => event.eventType === "effect.observed" || event.eventType === "effect.blocked_uncertain");
const settledEvents = events.filter((event) => event.eventType === "effect.settled");
for (const event of intentEvents) {
const attemptId = stringField(event, "attemptId");
const row = attemptById.get(attemptId);
if (!row) throw runtimeError("gate_attempt_pin_mismatch", run.runId, `effect.intent_recorded ${event.eventId} has no gate_attempts row`);
assertAttemptMatchesEvent(run, envelope, state, event, row);
}
for (const event of observedEvents) {
const attemptId = stringField(event, "attemptId");
const receipt = event.payload.receipt as { id?: unknown; hash?: unknown } | undefined;
const observationId = typeof receipt?.id === "string" ? receipt.id : undefined;
const row = observationByAttempt.get(attemptId);
if (!row || row.observationId !== observationId) {
throw runtimeError("gate_observation_missing", run.runId, `${event.eventType} has no matching observation row`);
}
if (row.recordedEventId !== event.eventId) {
throw runtimeError("gate_observation_missing", run.runId, "observation recorded_event_id does not match the event");
}
if (gateRowContentHash("gate_observations", row) !== row.contentHash) {
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "observation contentHash does not recompute");
}
if (typeof receipt?.hash === "string" && receipt.hash !== row.contentHash) {
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "observation receipt.hash does not match the row");
}
const attempt = attemptById.get(attemptId);
if (!attempt) throw runtimeError("gate_attempt_pin_mismatch", run.runId, "observation has no gate attempt");
assertShared(run, row, attempt);
assertClosedGateObservation(attempt.gateKind, row, row.observationId);
if (event.eventType === "effect.observed" && row.completeness !== "complete" && row.completeness !== "no-start") {
throw runtimeError("gate_observation_missing", run.runId, "effect.observed requires complete or no-start completeness");
}
if (event.eventType === "effect.blocked_uncertain" && row.completeness !== "incomplete") {
throw runtimeError("gate_observation_missing", run.runId, "blocked_uncertain requires incomplete completeness");
}
}
for (const event of settledEvents) {
const attemptId = stringField(event, "attemptId");
const refs = event.payload.evidenceRefs;
const evaluated = Array.isArray(refs) && refs.length > 0;
if (evaluated) {
const row = evidenceByAttempt.get(attemptId);
const ref = refs[0] as { id?: unknown; hash?: unknown };
if (!row || row.evidenceId !== ref.id) {
throw runtimeError("gate_evidence_missing", run.runId, "effect.settled has no matching evidence row");
}
if (row.settledEventId !== event.eventId) {
throw runtimeError("gate_evidence_missing", run.runId, "evidence settled_event_id does not match the event");
}
if (gateRowContentHash("gate_evidence", row) !== row.contentHash || (typeof ref.hash === "string" && ref.hash !== row.contentHash)) {
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "evidence hash does not recompute");
}
if (row.outcome !== event.payload.outcome) {
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "evidence outcome does not match the settled payload");
}
const attempt = attemptById.get(attemptId);
if (!attempt) throw runtimeError("gate_attempt_pin_mismatch", run.runId, "evidence has no gate attempt");
assertShared(run, row, attempt);
if (row.expect !== attempt.expect) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "evidence expect does not match the gate attempt");
}
const observation = observationByAttempt.get(attemptId);
if (!observation || observation.observationId !== row.observationId) {
throw runtimeError("gate_observation_missing", run.runId, "evidence observation_id does not match the attempt observation");
}
const recomputed = computeGateEvidenceOutcome(attempt.gateKind, attempt.expect, observation);
if (row.outcome !== recomputed || event.payload.outcome !== recomputed) {
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "evidence outcome does not match the observation and expect");
}
const step = envelope.plan.steps[row.stepId];
const required = step?.requiredEvidence.filter((item) => item.kind === "gate") ?? [];
const expectedKey = required.length === 1 ? required[0]!.key : null;
if (row.evidenceKey !== expectedKey) {
throw runtimeError("gate_evidence_unexpected", run.runId, "evidence_key does not match the declared requiredEvidence key");
}
} else {
if (evidenceByAttempt.has(attemptId)) {
throw runtimeError("gate_evidence_unexpected", run.runId, "proof-only settlement must not have a gate_evidence row");
}
const observation = observationByAttempt.get(attemptId);
const receipt = event.payload.receipt as { id?: unknown; hash?: unknown } | undefined;
if (!observation || observation.observationId !== receipt?.id || observation.contentHash !== receipt?.hash) {
throw runtimeError("gate_observation_missing", run.runId, "proof-only settlement does not bind the observation");
}
}
}
const namedAttemptIds = new Set(intentEvents.map((event) => stringField(event, "attemptId")));
for (const row of attempts) {
if (!namedAttemptIds.has(row.attemptId)) {
throw runtimeError("gate_evidence_orphan", run.runId, `gate_attempts row ${row.attemptId} is not named by effect.intent_recorded`);
}
if (row.runId !== run.runId) throw runtimeError("gate_evidence_orphan", run.runId, "gate attempt belongs to a foreign run");
if (gateRowContentHash("gate_attempts", row) !== row.contentHash) {
throw runtimeError("gate_evidence_hash_mismatch", run.runId, "gate attempt contentHash does not recompute");
}
}
for (const row of observations) {
if (!attemptById.has(row.attemptId)) {
throw runtimeError("gate_evidence_orphan", run.runId, `observation ${row.observationId} is owned by this run but names a foreign attempt`);
}
const event = events.find((item) => item.eventId === row.recordedEventId);
if (!event || (event.eventType !== "effect.observed" && event.eventType !== "effect.blocked_uncertain")) {
throw runtimeError("gate_evidence_orphan", run.runId, `observation ${row.observationId} is not named by an observation event`);
}
if (stringField(event, "attemptId") !== row.attemptId) {
throw runtimeError("gate_evidence_orphan", run.runId, `observation ${row.observationId} is not named by this attempt's observation event`);
}
const attempt = attemptById.get(row.attemptId)!;
assertShared(run, row, attempt);
assertClosedGateObservation(attempt.gateKind, row, row.observationId);
}
for (const row of evidence) {
if (!attemptById.has(row.attemptId)) {
throw runtimeError("gate_evidence_orphan", run.runId, `evidence ${row.evidenceId} is owned by this run but names a foreign attempt`);
}
const event = events.find((item) => item.eventId === row.settledEventId);
if (!event || event.eventType !== "effect.settled") {
throw runtimeError("gate_evidence_orphan", run.runId, `evidence ${row.evidenceId} is not named by effect.settled`);
}
if (stringField(event, "attemptId") !== row.attemptId) {
throw runtimeError("gate_evidence_orphan", run.runId, `evidence ${row.evidenceId} is not named by this attempt's settled event`);
}
const refs = event.payload.evidenceRefs;
if (!Array.isArray(refs) || refs.length !== 1 || (refs[0] as { id?: unknown }).id !== row.evidenceId) {
throw runtimeError("gate_evidence_orphan", run.runId, "evidence row is not the payload evidenceRef");
}
const attempt = attemptById.get(row.attemptId)!;
assertShared(run, row, attempt);
if (row.expect !== attempt.expect) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "evidence expect does not match the gate attempt");
}
}
if (attempts.length !== intentEvents.length) {
throw runtimeError("gate_evidence_orphan", run.runId, "gate attempt count does not match intent events");
}
if (observations.length !== observedEvents.length) {
throw runtimeError("gate_observation_missing", run.runId, "observation count does not match observation events");
}
const lastEffect = lastEffectByAttempt(events);
for (const [attemptId, last] of lastEffect) {
const evidenceRow = evidenceByAttempt.get(attemptId);
if (last === "effect.settled") {
const settled = settledEvents.filter((event) => stringField(event, "attemptId") === attemptId).at(-1);
const evaluated = Array.isArray(settled?.payload.evidenceRefs) && (settled!.payload.evidenceRefs as unknown[]).length > 0;
if (evaluated && !evidenceRow) throw runtimeError("gate_evidence_missing", run.runId, "evaluated settlement is missing evidence");
if (!evaluated && evidenceRow) throw runtimeError("gate_evidence_unexpected", run.runId, "proof-only settlement has evidence");
const capability = store.capabilityByAttempt(attemptId);
if (evaluated && capability?.state === "issued") {
throw runtimeError("gate_evidence_unexpected", run.runId, "settled attempt still has issued capability");
}
} else if (evidenceRow) {
throw runtimeError("gate_evidence_unexpected", run.runId, "non-settled attempt has evidence");
}
}
void kxmCanonicalJson;
}
function lastEffectByAttempt(events: readonly KxmRunEvent[]): Map<string, string> {
const last = new Map<string, string>();
for (const event of events) {
if (!event.eventType.startsWith("effect.")) continue;
const attemptId = event.payload.attemptId;
if (typeof attemptId === "string") last.set(attemptId, event.eventType);
}
return last;
}
function assertAttemptMatchesEvent(
run: KxmRunRecord,
envelope: KxmRunPlanEnvelope,
state: KxmRunState,
event: KxmRunEvent,
row: KxmGateAttemptRow,
): void {
const effectId = (event.payload.effect as { id?: unknown } | undefined)?.id;
if (row.effectId !== effectId || row.intentEventId !== event.eventId) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "intent event does not match the gate attempt");
}
if (row.runId !== run.runId || row.projectId !== run.projectId || row.homeRuntimeId !== run.homeRuntimeId) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "gate attempt owner does not match the run");
}
if (row.stepId !== event.payload.stepId || row.stepAttempt !== event.payload.stepAttempt || row.assignmentId !== event.payload.assignmentId || row.attemptId !== event.payload.attemptId) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "gate attempt identity does not match the intent payload");
}
const step = envelope.plan.steps[row.stepId];
if (!step || step.kind !== "gate") {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "gate attempt step is not a gate");
}
if (row.gateId !== step.gate || row.expect !== step.expect) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "gate attempt gate/expect do not match the plan");
}
const definition = envelope.gates.definitions[step.gate];
if (!definition || definition.kind !== row.gateKind) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "gate attempt kind does not match the pinned definition");
}
const expectedDefinitionHash = gateDefinitionHash(step.gate, definition as unknown as JsonValue);
if (row.gateDefinitionHash !== expectedDefinitionHash) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "gate_definition_hash does not match the pinned definition");
}
if (!envelope.gates.registry || row.registryHash !== envelope.gates.registry.hash) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "registry_hash does not match the pinned registry");
}
const planRowHash = envelope && state.runPlanHash;
if (row.runPlanHash !== planRowHash) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "run_plan_hash does not match the pinned plan");
}
if (row.controlProjectKey !== envelope.gates.controlRoot.projectKey) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "control_project_key does not match the envelope");
}
}
function assertShared(run: KxmRunRecord, row: KxmGateObservationRow | KxmGateEvidenceRow, attempt: KxmGateAttemptRow): void {
if (
row.runId !== attempt.runId
|| row.projectId !== attempt.projectId
|| row.homeRuntimeId !== attempt.homeRuntimeId
|| row.stepId !== attempt.stepId
|| row.stepAttempt !== attempt.stepAttempt
|| row.assignmentId !== attempt.assignmentId
|| row.effectId !== attempt.effectId
) {
throw runtimeError("gate_attempt_pin_mismatch", run.runId, "row identity does not match the gate attempt");
}
}
function stringField(event: KxmRunEvent, field: string): string {
const value = event.payload[field];
if (typeof value !== "string" || value.length === 0) {
throw runtimeError("gate_attempt_pin_mismatch", event.runId, `${event.eventType} is missing ${field}`);
}
return value;
}