{
  "version": 1,
  "id": "Circe-pr9-continuing",
  "repo": "/home/abstergo/Programming/Circe",
  "repoName": "Circe",
  "scope": "pr9-continuing",
  "title": "Circe PR 9 review and corrective implementation",
  "sinceCommit": "5dc176f80246d2ee672a166e9a8f5026c9aece5e",
  "lastReviewCommit": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
  "reviewRound": 1,
  "loc": {
    "added": 1451,
    "removed": 174
  },
  "updatedAt": "2026-09-24T03:40:33.371778+00:00",
  "htmlPath": "/home/abstergo/Documents/reviews/Circe-pr9-continuing-abbyview.html",
  "jsonPath": "/home/abstergo/Documents/reviews/Circe-pr9-continuing-abbyview.json",
  "verdict": "COMMENT",
  "baselineVerdict": "CHANGES_REQUESTED",
  "actionableOpen": 0,
  "productComplete": false,
  "prUrl": "https://github.com/Absterrg0/circe/pull/10",
  "reviewedPrUrl": "https://github.com/Absterrg0/circe/pull/9",
  "assessment": "Eight implementation gaps addressed in a separate corrective PR. Product completeness is not established; live validation is blocked and application language workflows remain.",
  "baseline": {
    "frozenAt": "2026-09-24T03:40:33.371778+00:00",
    "title": "Circe PR 9",
    "lede": "Durable context is implemented, but restart semantics and evaluation needed correction.",
    "walkthrough": {
      "prose": "PR review plus authorized remediation.",
      "bullets": [
        "Reviewed actual PR #9 head 5dc176f80246d2ee672a166e9a8f5026c9aece5e on top of merged PR #8.",
        "Used three requested GPT-6 Luna agents for bounded host, persistence and transport work; the primary agent reviewed their edits and completed follow-up validation after agents reached their usage limit.",
        "Kept the original checkout clean; fixes are in an isolated worktree and a separate PR stacked on #9. Neither PR was merged.",
        "Baseline: 438 passing tests. Corrective head: 466 tests, typecheck and package-boundary checks pass. The five fixture cases and continuing restart demo pass.",
        "Re-ran a 40-case selection against the original PR: 17 expected failures and 23 passes. Three failures exercise missing control/client APIs; counts are not distinct defects.",
        "No live model call was made. Remaining product gaps are listed separately from fixed implementation findings."
      ]
    },
    "filesChanged": [
      {
        "path": "README.md",
        "added": 38,
        "removed": 29,
        "delta": "+38 \u221229"
      },
      {
        "path": "apps/inspector/package.json",
        "added": 4,
        "removed": 2,
        "delta": "+4 \u22122"
      },
      {
        "path": "apps/inspector/src/file-host.ts",
        "added": 61,
        "removed": 25,
        "delta": "+61 \u221225"
      },
      {
        "path": "apps/inspector/src/heldout-cases.ts",
        "added": 36,
        "removed": 1,
        "delta": "+36 \u22121"
      },
      {
        "path": "apps/inspector/src/heldout-review.test.ts",
        "added": 73,
        "removed": 0,
        "delta": "+73 \u22120"
      },
      {
        "path": "apps/inspector/src/heldout.ts",
        "added": 63,
        "removed": 31,
        "delta": "+63 \u221231"
      },
      {
        "path": "apps/inspector/src/host.test.ts",
        "added": 252,
        "removed": 0,
        "delta": "+252 \u22120"
      },
      {
        "path": "apps/inspector/src/host.ts",
        "added": 125,
        "removed": 36,
        "delta": "+125 \u221236"
      },
      {
        "path": "apps/inspector/src/live-cli.ts",
        "added": 26,
        "removed": 0,
        "delta": "+26 \u22120"
      },
      {
        "path": "apps/inspector/src/live-evaluation.test.ts",
        "added": 26,
        "removed": 0,
        "delta": "+26 \u22120"
      },
      {
        "path": "apps/inspector/src/turn-store.test.ts",
        "added": 96,
        "removed": 0,
        "delta": "+96 \u22120"
      },
      {
        "path": "apps/inspector/src/turn-store.ts",
        "added": 169,
        "removed": 0,
        "delta": "+169 \u22120"
      },
      {
        "path": "docs/SEMANTIC-CONTEXT-MODEL.md",
        "added": 10,
        "removed": 7,
        "delta": "+10 \u22127"
      },
      {
        "path": "packages/context-file/src/codec.ts",
        "added": 13,
        "removed": 2,
        "delta": "+13 \u22122"
      },
      {
        "path": "packages/context-file/src/index.test.ts",
        "added": 50,
        "removed": 1,
        "delta": "+50 \u22121"
      },
      {
        "path": "packages/context-file/src/index.ts",
        "added": 37,
        "removed": 27,
        "delta": "+37 \u221227"
      },
      {
        "path": "packages/file-log/src/index.test.ts",
        "added": 22,
        "removed": 1,
        "delta": "+22 \u22121"
      },
      {
        "path": "packages/file-log/src/index.ts",
        "added": 33,
        "removed": 8,
        "delta": "+33 \u22128"
      },
      {
        "path": "packages/perception-jev/README.md",
        "added": 26,
        "removed": 0,
        "delta": "+26 \u22120"
      },
      {
        "path": "packages/perception-jev/src/backend.ts",
        "added": 10,
        "removed": 4,
        "delta": "+10 \u22124"
      },
      {
        "path": "packages/perception-jev/src/index.ts",
        "added": 1,
        "removed": 0,
        "delta": "+1 \u22120"
      },
      {
        "path": "packages/perception-jev/src/perception-jev.test.ts",
        "added": 151,
        "removed": 0,
        "delta": "+151 \u22120"
      },
      {
        "path": "packages/perception-jev/src/typesafe-client.ts",
        "added": 126,
        "removed": 0,
        "delta": "+126 \u22120"
      },
      {
        "path": "pnpm-lock.yaml",
        "added": 3,
        "removed": 0,
        "delta": "+3 \u22120"
      }
    ],
    "flowDiagram": "Observation identity \u2192 serialized host \u2192 authority observations \u2192 immutable frame \u2192 bounded perception \u2192 first interpretation recorded\n\u2192 decision traces persisted \u2192 accepted-turn compiler commands \u2192 kernel authorization / journal \u2192 executor\n\nRetry / restart \u2192 pinned interpretation \u2192 identical command IDs \u2192 kernel deduplication / reconciliation",
    "sinceCommit": "5dc176f80246d2ee672a166e9a8f5026c9aece5e",
    "loc": {
      "added": 1451,
      "removed": 174
    }
  },
  "findings": [
    {
      "id": "F77",
      "title": "Persist the decisions that justified commands",
      "file": "apps/inspector/src/host.ts",
      "lines": "370",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "data-integrity",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "PR #9 records frames and observations, but runTurn never writes run.traces to its semantic store. The durable context adapter can store decisions, yet the real host omits them. A successful operation therefore survives restart without its original decision evidence in the context store.",
        "The corrected host persists the actual decision traces before entering the command loop and permits only identical provenance on a retry. Two existing host tests gained assertions that fail on the original PR. Restart and first-turn execution retain the same trace identities."
      ],
      "tests": "host.test.ts checks decision persistence on initial execution and after reopening.",
      "suggestedDiff": "diff --git a/apps/inspector/src/host.ts b/apps/inspector/src/host.ts\nindex fa2e83f..65136a8 100644\n--- a/apps/inspector/src/host.ts\n+++ b/apps/inspector/src/host.ts\n@@ -5,9 +5,11 @@ import {\n   type CapabilityDescriptor,\n   type CapabilityExecutor,\n   type EntityRef,\n+  type EffectId,\n   type Intent,\n   type InteractionJournal,\n   type InteractionState,\n+  type KernelCommand,\n   type Observation,\n   type Referent,\n   type Transaction,\n@@ -18,6 +20,7 @@ import {\n   ingestWorldView,\n   type CapabilitySemantics,\n   type ContextFrame,\n+  type DecisionTrace,\n   type FrameInteraction,\n   type FrameLiveObligation,\n   type FramePendingIntent,\n@@ -35,6 +38,11 @@ import {\n   type WorkflowDefinition,\n   type WorkflowRun,\n } from \"@circe/perception\";\n+import {\n+  createInMemoryAcceptedTurnStore,\n+  type AcceptedTurnStore,\n+  type AcceptedTurnRecord,\n+} from \"./turn-store\";\n \n /**\n  * Continuing interaction host.\n@@ -70,6 +78,8 @@ export interface ContinuingInteractionConfig {\n   /** durable or in-memory; the host never closes what it does not own */\n   context: SemanticContextStoreContract;\n   journal: InteractionJournal;\n+  /** optional durable pin for the first interpretation of each observation */\n+  turnStore?: AcceptedTurnStore;\n   capabilities: CapabilitySemantics[];\n   workflow: WorkflowDefinition;\n   backend: PerceptionBackend;\n@@ -105,6 +115,10 @@ export interface ContinuingTurnReport {\n export interface ContinuingInteractionHost {\n   readonly context: SemanticContextStoreContract;\n   runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport>;\n+  /** Drive a typed kernel command (confirm/cancel/resume/revise/propose). */\n+  command(command: KernelCommand, now: string): Promise<TurnResult>;\n+  /** Resolve an UNKNOWN dispatch from its recorded receipt. */\n+  reconcile(effectId: EffectId, now: string): Promise<TurnResult>;\n   /**\n    * A non-language kernel input turn (UI focus/selection, world events).\n    * Language never goes through here; the perception path is `runTurn`.\n@@ -215,6 +229,15 @@ export function createContinuingInteractionHost(\n   config: ContinuingInteractionConfig,\n ): ContinuingInteractionHost {\n   let clockNow: string | undefined;\n+  const turnStore = config.turnStore ?? createInMemoryAcceptedTurnStore();\n+  // A continuing interaction has one discourse timeline. Serialize ingestion\n+  // and language turns so each frame is grounded in the previous turn's state.\n+  let turnQueue: Promise<void> = Promise.resolve();\n+  function enqueue<T>(work: () => Promise<T>): Promise<T> {\n+    const result = turnQueue.then(work);\n+    turnQueue = result.then(() => undefined, () => undefined);\n+    return result;\n+  }\n   const runtime = createRuntime({\n     ports: {\n       authorityFor: (ref) => {\n@@ -256,51 +279,117 @@ export function createContinuingInteractionHost(\n     context: config.context,\n     state: () => runtime.state(),\n \n-    async ingest(observation: Observation): Promise<TurnResult> {\n-      clockNow = observation.at;\n-      return runtime.ingest(observation);\n+    ingest(observation: Observation): Promise<TurnResult> {\n+      const stableObservation = structuredClone(observation);\n+      return enqueue(async () => {\n+        clockNow = stableObservation.at;\n+        return runtime.ingest(stableObservation);\n+      });\n     },\n \n-    async runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n-      clockNow = input.now;\n-      const state = runtime.state();\n-      const scopeId = state.scopeStack[state.scopeStack.length - 1];\n-      const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n-      const refs = input.refs ?? scopeRefs;\n-      await observe(refs);\n-\n-      const { frame } = await assembleFrame(config.context, {\n-        interactionId: config.interactionId,\n-        observationId: input.observationId,\n-        utterance: { text: input.utterance, source: \"text\" },\n-        interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n-        capabilities: config.capabilities,\n-        now: input.now,\n+    command(command: KernelCommand, now: string): Promise<TurnResult> {\n+      const stableCommand = structuredClone(command);\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.command(stableCommand);\n       });\n+    },\n \n-      const run = await runPerceptionWorkflow({\n-        backend: config.backend,\n-        frame,\n-        workflow: config.workflow,\n-        policy: config.policy,\n-        now: input.now,\n-        runId: `host:${config.interactionId}:${input.observationId}`,\n+    reconcile(effectId: EffectId, now: string): Promise<TurnResult> {\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.reconcile(effectId);\n       });\n+    },\n \n-      const turn = semanticTurnFromRun(run, config.propositions(run, frame));\n-      const compiled = compileSemanticTurn(turn, {\n-        frame,\n-        commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n-      });\n+    runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n+      const stableInput = structuredClone(input);\n+      return enqueue(async () => {\n+        const input = stableInput;\n+        const key = `${config.interactionId}:${input.observationId}`;\n+        const inputFingerprint = JSON.stringify({\n+          observationId: input.observationId,\n+          utterance: input.utterance,\n+          now: input.now,\n+          refs: input.refs === undefined\n+            ? \"<active-scope>\"\n+            : input.refs.map((ref) => ({\n+                namespace: ref.namespace,\n+                type: ref.type,\n+                id: ref.id,\n+                authority: ref.authority,\n+              })),\n+        });\n+        const pinned = turnStore.get(key);\n+        if (pinned && pinned.inputFingerprint !== inputFingerprint) {\n+          throw new Error(`observation identity ${input.observationId} was reused with different turn input`);\n+        }\n \n-      config.beforeCommands?.(frame);\n+        clockNow = input.now;\n+        let frame: ContextFrame;\n+        let run: WorkflowRun;\n+        let turn: SemanticTurn;\n+        let compiled: CompiledTurn;\n+        if (pinned) {\n+          ({ frame, run, turn, compiled } = pinned);\n+        } else {\n+          const state = runtime.state();\n+          const scopeId = state.scopeStack[state.scopeStack.length - 1];\n+          const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n+          const refs = input.refs ?? scopeRefs;\n+          await observe(refs);\n \n-      const results: TurnResult[] = [];\n-      for (const command of compiled.commands) {\n-        results.push(await runtime.command(command));\n-      }\n+          ({ frame } = await assembleFrame(config.context, {\n+            interactionId: config.interactionId,\n+            observationId: input.observationId,\n+            utterance: { text: input.utterance, source: \"text\" },\n+            interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n+            capabilities: config.capabilities,\n+            now: input.now,\n+          }));\n+\n+          run = await runPerceptionWorkflow({\n+            backend: config.backend,\n+            frame,\n+            workflow: config.workflow,\n+            policy: config.policy,\n+            now: input.now,\n+            runId: `host:${config.interactionId}:${frame.id}`,\n+          });\n+\n+          turn = semanticTurnFromRun(run, config.propositions(run, frame));\n+          compiled = compileSemanticTurn(turn, {\n+            frame,\n+            commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n+          });\n+          const record: AcceptedTurnRecord = { key, inputFingerprint, frame, run, turn, compiled };\n+          // Pin the first interpretation before trace writes or any command dispatch.\n+          turnStore.record(record);\n+        }\n \n-      return { frame, run, turn, compiled, results, state: runtime.state() };\n+        persistDecisionTraces(config.context, run.traces);\n+        config.beforeCommands?.(frame);\n+\n+        const results: TurnResult[] = [];\n+        for (const command of compiled.commands) {\n+          results.push(await runtime.command(command));\n+        }\n+\n+        return { frame, run, turn, compiled, results, state: runtime.state() };\n+      });\n     },\n   };\n }\n+\n+function persistDecisionTraces(store: SemanticContextStoreContract, traces: DecisionTrace[]): void {\n+  for (const trace of traces) {\n+    const existing = store.decision(trace.id);\n+    if (existing) {\n+      if (JSON.stringify(existing) !== JSON.stringify(trace)) {\n+        throw new Error(`decision identity ${trace.id} was reused with different provenance`);\n+      }\n+      continue;\n+    }\n+    store.recordDecision(trace);\n+  }\n+}\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn apps/inspector/src/host.ts around line 370, verify persist the decisions that justified commands against the baseline 5dc176f and corrective commit 927c9ea. PR #9 records frames and observations, but runTurn never writes run.traces to its semantic store. The durable context adapter can store decisions, yet the real host omits them. A successful operation therefore survives restart without its original decision evidence in the context store. The corrected host persists the actual decision traces before entering the command loop and permits only identical provenance on a retry. Two existing host tests gained assertions that fail on the original PR. Restart and first-turn execution retain the same trace identities. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. host.test.ts checks decision persistence on initial execution and after reopening."
    },
    {
      "id": "F78",
      "title": "Reuse the first interpretation when an observation is retried",
      "file": "apps/inspector/src/host.ts",
      "lines": "323",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "correctness",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "PR #9 invokes perception again on every runTurn delivery. Stable kernel command IDs cannot protect an observation that originally abstained, and they do not pin the number or meaning of a later interpretation. Retrying after a restart can ask a nondeterministic model to turn an earlier abstention into new work.",
        "The host now appends the first interpreted turn before trace writes or dispatch. Reopening uses that snapshot, checks the input fingerprint, and redelivers exactly its compiled commands. This also covers a failure immediately before dispatch. The log preserves workflow Maps explicitly; recovery checks provenance and recompiles the saved semantic turn before accepting its commands."
      ],
      "tests": "host.test.ts covers completed replay, abstention, conflicting input and the pre-dispatch cut; turn-store.test.ts covers Maps, detached reads, conflicts and corrupt records.",
      "suggestedDiff": "diff --git a/apps/inspector/src/host.ts b/apps/inspector/src/host.ts\nindex fa2e83f..65136a8 100644\n--- a/apps/inspector/src/host.ts\n+++ b/apps/inspector/src/host.ts\n@@ -5,9 +5,11 @@ import {\n   type CapabilityDescriptor,\n   type CapabilityExecutor,\n   type EntityRef,\n+  type EffectId,\n   type Intent,\n   type InteractionJournal,\n   type InteractionState,\n+  type KernelCommand,\n   type Observation,\n   type Referent,\n   type Transaction,\n@@ -18,6 +20,7 @@ import {\n   ingestWorldView,\n   type CapabilitySemantics,\n   type ContextFrame,\n+  type DecisionTrace,\n   type FrameInteraction,\n   type FrameLiveObligation,\n   type FramePendingIntent,\n@@ -35,6 +38,11 @@ import {\n   type WorkflowDefinition,\n   type WorkflowRun,\n } from \"@circe/perception\";\n+import {\n+  createInMemoryAcceptedTurnStore,\n+  type AcceptedTurnStore,\n+  type AcceptedTurnRecord,\n+} from \"./turn-store\";\n \n /**\n  * Continuing interaction host.\n@@ -70,6 +78,8 @@ export interface ContinuingInteractionConfig {\n   /** durable or in-memory; the host never closes what it does not own */\n   context: SemanticContextStoreContract;\n   journal: InteractionJournal;\n+  /** optional durable pin for the first interpretation of each observation */\n+  turnStore?: AcceptedTurnStore;\n   capabilities: CapabilitySemantics[];\n   workflow: WorkflowDefinition;\n   backend: PerceptionBackend;\n@@ -105,6 +115,10 @@ export interface ContinuingTurnReport {\n export interface ContinuingInteractionHost {\n   readonly context: SemanticContextStoreContract;\n   runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport>;\n+  /** Drive a typed kernel command (confirm/cancel/resume/revise/propose). */\n+  command(command: KernelCommand, now: string): Promise<TurnResult>;\n+  /** Resolve an UNKNOWN dispatch from its recorded receipt. */\n+  reconcile(effectId: EffectId, now: string): Promise<TurnResult>;\n   /**\n    * A non-language kernel input turn (UI focus/selection, world events).\n    * Language never goes through here; the perception path is `runTurn`.\n@@ -215,6 +229,15 @@ export function createContinuingInteractionHost(\n   config: ContinuingInteractionConfig,\n ): ContinuingInteractionHost {\n   let clockNow: string | undefined;\n+  const turnStore = config.turnStore ?? createInMemoryAcceptedTurnStore();\n+  // A continuing interaction has one discourse timeline. Serialize ingestion\n+  // and language turns so each frame is grounded in the previous turn's state.\n+  let turnQueue: Promise<void> = Promise.resolve();\n+  function enqueue<T>(work: () => Promise<T>): Promise<T> {\n+    const result = turnQueue.then(work);\n+    turnQueue = result.then(() => undefined, () => undefined);\n+    return result;\n+  }\n   const runtime = createRuntime({\n     ports: {\n       authorityFor: (ref) => {\n@@ -256,51 +279,117 @@ export function createContinuingInteractionHost(\n     context: config.context,\n     state: () => runtime.state(),\n \n-    async ingest(observation: Observation): Promise<TurnResult> {\n-      clockNow = observation.at;\n-      return runtime.ingest(observation);\n+    ingest(observation: Observation): Promise<TurnResult> {\n+      const stableObservation = structuredClone(observation);\n+      return enqueue(async () => {\n+        clockNow = stableObservation.at;\n+        return runtime.ingest(stableObservation);\n+      });\n     },\n \n-    async runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n-      clockNow = input.now;\n-      const state = runtime.state();\n-      const scopeId = state.scopeStack[state.scopeStack.length - 1];\n-      const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n-      const refs = input.refs ?? scopeRefs;\n-      await observe(refs);\n-\n-      const { frame } = await assembleFrame(config.context, {\n-        interactionId: config.interactionId,\n-        observationId: input.observationId,\n-        utterance: { text: input.utterance, source: \"text\" },\n-        interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n-        capabilities: config.capabilities,\n-        now: input.now,\n+    command(command: KernelCommand, now: string): Promise<TurnResult> {\n+      const stableCommand = structuredClone(command);\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.command(stableCommand);\n       });\n+    },\n \n-      const run = await runPerceptionWorkflow({\n-        backend: config.backend,\n-        frame,\n-        workflow: config.workflow,\n-        policy: config.policy,\n-        now: input.now,\n-        runId: `host:${config.interactionId}:${input.observationId}`,\n+    reconcile(effectId: EffectId, now: string): Promise<TurnResult> {\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.reconcile(effectId);\n       });\n+    },\n \n-      const turn = semanticTurnFromRun(run, config.propositions(run, frame));\n-      const compiled = compileSemanticTurn(turn, {\n-        frame,\n-        commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n-      });\n+    runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n+      const stableInput = structuredClone(input);\n+      return enqueue(async () => {\n+        const input = stableInput;\n+        const key = `${config.interactionId}:${input.observationId}`;\n+        const inputFingerprint = JSON.stringify({\n+          observationId: input.observationId,\n+          utterance: input.utterance,\n+          now: input.now,\n+          refs: input.refs === undefined\n+            ? \"<active-scope>\"\n+            : input.refs.map((ref) => ({\n+                namespace: ref.namespace,\n+                type: ref.type,\n+                id: ref.id,\n+                authority: ref.authority,\n+              })),\n+        });\n+        const pinned = turnStore.get(key);\n+        if (pinned && pinned.inputFingerprint !== inputFingerprint) {\n+          throw new Error(`observation identity ${input.observationId} was reused with different turn input`);\n+        }\n \n-      config.beforeCommands?.(frame);\n+        clockNow = input.now;\n+        let frame: ContextFrame;\n+        let run: WorkflowRun;\n+        let turn: SemanticTurn;\n+        let compiled: CompiledTurn;\n+        if (pinned) {\n+          ({ frame, run, turn, compiled } = pinned);\n+        } else {\n+          const state = runtime.state();\n+          const scopeId = state.scopeStack[state.scopeStack.length - 1];\n+          const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n+          const refs = input.refs ?? scopeRefs;\n+          await observe(refs);\n \n-      const results: TurnResult[] = [];\n-      for (const command of compiled.commands) {\n-        results.push(await runtime.command(command));\n-      }\n+          ({ frame } = await assembleFrame(config.context, {\n+            interactionId: config.interactionId,\n+            observationId: input.observationId,\n+            utterance: { text: input.utterance, source: \"text\" },\n+            interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n+            capabilities: config.capabilities,\n+            now: input.now,\n+          }));\n+\n+          run = await runPerceptionWorkflow({\n+            backend: config.backend,\n+            frame,\n+            workflow: config.workflow,\n+            policy: config.policy,\n+            now: input.now,\n+            runId: `host:${config.interactionId}:${frame.id}`,\n+          });\n+\n+          turn = semanticTurnFromRun(run, config.propositions(run, frame));\n+          compiled = compileSemanticTurn(turn, {\n+            frame,\n+            commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n+          });\n+          const record: AcceptedTurnRecord = { key, inputFingerprint, frame, run, turn, compiled };\n+          // Pin the first interpretation before trace writes or any command dispatch.\n+          turnStore.record(record);\n+        }\n \n-      return { frame, run, turn, compiled, results, state: runtime.state() };\n+        persistDecisionTraces(config.context, run.traces);\n+        config.beforeCommands?.(frame);\n+\n+        const results: TurnResult[] = [];\n+        for (const command of compiled.commands) {\n+          results.push(await runtime.command(command));\n+        }\n+\n+        return { frame, run, turn, compiled, results, state: runtime.state() };\n+      });\n     },\n   };\n }\n+\n+function persistDecisionTraces(store: SemanticContextStoreContract, traces: DecisionTrace[]): void {\n+  for (const trace of traces) {\n+    const existing = store.decision(trace.id);\n+    if (existing) {\n+      if (JSON.stringify(existing) !== JSON.stringify(trace)) {\n+        throw new Error(`decision identity ${trace.id} was reused with different provenance`);\n+      }\n+      continue;\n+    }\n+    store.recordDecision(trace);\n+  }\n+}\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn apps/inspector/src/host.ts around line 323, verify reuse the first interpretation when an observation is retried against the baseline 5dc176f and corrective commit 927c9ea. PR #9 invokes perception again on every runTurn delivery. Stable kernel command IDs cannot protect an observation that originally abstained, and they do not pin the number or meaning of a later interpretation. Retrying after a restart can ask a nondeterministic model to turn an earlier abstention into new work. The host now appends the first interpreted turn before trace writes or dispatch. Reopening uses that snapshot, checks the input fingerprint, and redelivers exactly its compiled commands. This also covers a failure immediately before dispatch. The log preserves workflow Maps explicitly; recovery checks provenance and recompiles the saved semantic turn before accepting its commands. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. host.test.ts covers completed replay, abstention, conflicting input and the pre-dispatch cut; turn-store.test.ts covers Maps, detached reads, conflicts and corrupt records."
    },
    {
      "id": "F79",
      "title": "Serialize calls that share the interaction clock and discourse",
      "file": "apps/inspector/src/host.ts",
      "lines": "236",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "correctness",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "The original host lets simultaneous runTurn and ingest calls operate on one runtime and mutable clock. A later call can change the time while an earlier perception call is suspended. Both turns can assemble against discourse that has not incorporated the other operation.",
        "A single per-host promise queue now orders turns, observations, commands and reconciliation. Inputs are cloned before joining the queue, so caller mutation during the wait cannot change the operation. A concurrency regression observes overlapping backend work on PR #9 and a maximum of one active turn after the fix."
      ],
      "tests": "host.test.ts checks simultaneous calls, pre-enqueue snapshots and persisted provenance.",
      "suggestedDiff": "diff --git a/apps/inspector/src/host.ts b/apps/inspector/src/host.ts\nindex fa2e83f..65136a8 100644\n--- a/apps/inspector/src/host.ts\n+++ b/apps/inspector/src/host.ts\n@@ -5,9 +5,11 @@ import {\n   type CapabilityDescriptor,\n   type CapabilityExecutor,\n   type EntityRef,\n+  type EffectId,\n   type Intent,\n   type InteractionJournal,\n   type InteractionState,\n+  type KernelCommand,\n   type Observation,\n   type Referent,\n   type Transaction,\n@@ -18,6 +20,7 @@ import {\n   ingestWorldView,\n   type CapabilitySemantics,\n   type ContextFrame,\n+  type DecisionTrace,\n   type FrameInteraction,\n   type FrameLiveObligation,\n   type FramePendingIntent,\n@@ -35,6 +38,11 @@ import {\n   type WorkflowDefinition,\n   type WorkflowRun,\n } from \"@circe/perception\";\n+import {\n+  createInMemoryAcceptedTurnStore,\n+  type AcceptedTurnStore,\n+  type AcceptedTurnRecord,\n+} from \"./turn-store\";\n \n /**\n  * Continuing interaction host.\n@@ -70,6 +78,8 @@ export interface ContinuingInteractionConfig {\n   /** durable or in-memory; the host never closes what it does not own */\n   context: SemanticContextStoreContract;\n   journal: InteractionJournal;\n+  /** optional durable pin for the first interpretation of each observation */\n+  turnStore?: AcceptedTurnStore;\n   capabilities: CapabilitySemantics[];\n   workflow: WorkflowDefinition;\n   backend: PerceptionBackend;\n@@ -105,6 +115,10 @@ export interface ContinuingTurnReport {\n export interface ContinuingInteractionHost {\n   readonly context: SemanticContextStoreContract;\n   runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport>;\n+  /** Drive a typed kernel command (confirm/cancel/resume/revise/propose). */\n+  command(command: KernelCommand, now: string): Promise<TurnResult>;\n+  /** Resolve an UNKNOWN dispatch from its recorded receipt. */\n+  reconcile(effectId: EffectId, now: string): Promise<TurnResult>;\n   /**\n    * A non-language kernel input turn (UI focus/selection, world events).\n    * Language never goes through here; the perception path is `runTurn`.\n@@ -215,6 +229,15 @@ export function createContinuingInteractionHost(\n   config: ContinuingInteractionConfig,\n ): ContinuingInteractionHost {\n   let clockNow: string | undefined;\n+  const turnStore = config.turnStore ?? createInMemoryAcceptedTurnStore();\n+  // A continuing interaction has one discourse timeline. Serialize ingestion\n+  // and language turns so each frame is grounded in the previous turn's state.\n+  let turnQueue: Promise<void> = Promise.resolve();\n+  function enqueue<T>(work: () => Promise<T>): Promise<T> {\n+    const result = turnQueue.then(work);\n+    turnQueue = result.then(() => undefined, () => undefined);\n+    return result;\n+  }\n   const runtime = createRuntime({\n     ports: {\n       authorityFor: (ref) => {\n@@ -256,51 +279,117 @@ export function createContinuingInteractionHost(\n     context: config.context,\n     state: () => runtime.state(),\n \n-    async ingest(observation: Observation): Promise<TurnResult> {\n-      clockNow = observation.at;\n-      return runtime.ingest(observation);\n+    ingest(observation: Observation): Promise<TurnResult> {\n+      const stableObservation = structuredClone(observation);\n+      return enqueue(async () => {\n+        clockNow = stableObservation.at;\n+        return runtime.ingest(stableObservation);\n+      });\n     },\n \n-    async runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n-      clockNow = input.now;\n-      const state = runtime.state();\n-      const scopeId = state.scopeStack[state.scopeStack.length - 1];\n-      const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n-      const refs = input.refs ?? scopeRefs;\n-      await observe(refs);\n-\n-      const { frame } = await assembleFrame(config.context, {\n-        interactionId: config.interactionId,\n-        observationId: input.observationId,\n-        utterance: { text: input.utterance, source: \"text\" },\n-        interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n-        capabilities: config.capabilities,\n-        now: input.now,\n+    command(command: KernelCommand, now: string): Promise<TurnResult> {\n+      const stableCommand = structuredClone(command);\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.command(stableCommand);\n       });\n+    },\n \n-      const run = await runPerceptionWorkflow({\n-        backend: config.backend,\n-        frame,\n-        workflow: config.workflow,\n-        policy: config.policy,\n-        now: input.now,\n-        runId: `host:${config.interactionId}:${input.observationId}`,\n+    reconcile(effectId: EffectId, now: string): Promise<TurnResult> {\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.reconcile(effectId);\n       });\n+    },\n \n-      const turn = semanticTurnFromRun(run, config.propositions(run, frame));\n-      const compiled = compileSemanticTurn(turn, {\n-        frame,\n-        commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n-      });\n+    runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n+      const stableInput = structuredClone(input);\n+      return enqueue(async () => {\n+        const input = stableInput;\n+        const key = `${config.interactionId}:${input.observationId}`;\n+        const inputFingerprint = JSON.stringify({\n+          observationId: input.observationId,\n+          utterance: input.utterance,\n+          now: input.now,\n+          refs: input.refs === undefined\n+            ? \"<active-scope>\"\n+            : input.refs.map((ref) => ({\n+                namespace: ref.namespace,\n+                type: ref.type,\n+                id: ref.id,\n+                authority: ref.authority,\n+              })),\n+        });\n+        const pinned = turnStore.get(key);\n+        if (pinned && pinned.inputFingerprint !== inputFingerprint) {\n+          throw new Error(`observation identity ${input.observationId} was reused with different turn input`);\n+        }\n \n-      config.beforeCommands?.(frame);\n+        clockNow = input.now;\n+        let frame: ContextFrame;\n+        let run: WorkflowRun;\n+        let turn: SemanticTurn;\n+        let compiled: CompiledTurn;\n+        if (pinned) {\n+          ({ frame, run, turn, compiled } = pinned);\n+        } else {\n+          const state = runtime.state();\n+          const scopeId = state.scopeStack[state.scopeStack.length - 1];\n+          const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n+          const refs = input.refs ?? scopeRefs;\n+          await observe(refs);\n \n-      const results: TurnResult[] = [];\n-      for (const command of compiled.commands) {\n-        results.push(await runtime.command(command));\n-      }\n+          ({ frame } = await assembleFrame(config.context, {\n+            interactionId: config.interactionId,\n+            observationId: input.observationId,\n+            utterance: { text: input.utterance, source: \"text\" },\n+            interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n+            capabilities: config.capabilities,\n+            now: input.now,\n+          }));\n+\n+          run = await runPerceptionWorkflow({\n+            backend: config.backend,\n+            frame,\n+            workflow: config.workflow,\n+            policy: config.policy,\n+            now: input.now,\n+            runId: `host:${config.interactionId}:${frame.id}`,\n+          });\n+\n+          turn = semanticTurnFromRun(run, config.propositions(run, frame));\n+          compiled = compileSemanticTurn(turn, {\n+            frame,\n+            commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n+          });\n+          const record: AcceptedTurnRecord = { key, inputFingerprint, frame, run, turn, compiled };\n+          // Pin the first interpretation before trace writes or any command dispatch.\n+          turnStore.record(record);\n+        }\n \n-      return { frame, run, turn, compiled, results, state: runtime.state() };\n+        persistDecisionTraces(config.context, run.traces);\n+        config.beforeCommands?.(frame);\n+\n+        const results: TurnResult[] = [];\n+        for (const command of compiled.commands) {\n+          results.push(await runtime.command(command));\n+        }\n+\n+        return { frame, run, turn, compiled, results, state: runtime.state() };\n+      });\n     },\n   };\n }\n+\n+function persistDecisionTraces(store: SemanticContextStoreContract, traces: DecisionTrace[]): void {\n+  for (const trace of traces) {\n+    const existing = store.decision(trace.id);\n+    if (existing) {\n+      if (JSON.stringify(existing) !== JSON.stringify(trace)) {\n+        throw new Error(`decision identity ${trace.id} was reused with different provenance`);\n+      }\n+      continue;\n+    }\n+    store.recordDecision(trace);\n+  }\n+}\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn apps/inspector/src/host.ts around line 236, verify serialize calls that share the interaction clock and discourse against the baseline 5dc176f and corrective commit 927c9ea. The original host lets simultaneous runTurn and ingest calls operate on one runtime and mutable clock. A later call can change the time while an earlier perception call is suspended. Both turns can assemble against discourse that has not incorporated the other operation. A single per-host promise queue now orders turns, observations, commands and reconciliation. Inputs are cloned before joining the queue, so caller mutation during the wait cannot change the operation. A concurrency regression observes overlapping backend work on PR #9 and a maximum of one active turn after the fix. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. host.test.ts checks simultaneous calls, pre-enqueue snapshots and persisted provenance."
    },
    {
      "id": "F80",
      "title": "Expose recovery and confirmation through the continuing host",
      "file": "apps/inspector/src/host.ts",
      "lines": "121",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "correctness",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "The file host can reconstruct a confirmation gate or UNKNOWN effect, but its public API only offers ingestion and request-oriented semantic turns. The underlying runtime is private. Application callers cannot use the kernel\u2019s typed confirm or reconcile methods through this host.",
        "The host now exposes command and reconcile, using the same queue and clock as other operations. Retained tests restart before confirmation and after a lost invocation response; confirmation executes once and reconciliation resolves the recorded application without invoking again. This supplies typed controls. It does not implement an English confirmation/correction workflow."
      ],
      "tests": "host.test.ts exercises both missing APIs through real file-backed restart; both fail as absent methods on the original PR.",
      "suggestedDiff": "diff --git a/apps/inspector/src/host.ts b/apps/inspector/src/host.ts\nindex fa2e83f..65136a8 100644\n--- a/apps/inspector/src/host.ts\n+++ b/apps/inspector/src/host.ts\n@@ -5,9 +5,11 @@ import {\n   type CapabilityDescriptor,\n   type CapabilityExecutor,\n   type EntityRef,\n+  type EffectId,\n   type Intent,\n   type InteractionJournal,\n   type InteractionState,\n+  type KernelCommand,\n   type Observation,\n   type Referent,\n   type Transaction,\n@@ -18,6 +20,7 @@ import {\n   ingestWorldView,\n   type CapabilitySemantics,\n   type ContextFrame,\n+  type DecisionTrace,\n   type FrameInteraction,\n   type FrameLiveObligation,\n   type FramePendingIntent,\n@@ -35,6 +38,11 @@ import {\n   type WorkflowDefinition,\n   type WorkflowRun,\n } from \"@circe/perception\";\n+import {\n+  createInMemoryAcceptedTurnStore,\n+  type AcceptedTurnStore,\n+  type AcceptedTurnRecord,\n+} from \"./turn-store\";\n \n /**\n  * Continuing interaction host.\n@@ -70,6 +78,8 @@ export interface ContinuingInteractionConfig {\n   /** durable or in-memory; the host never closes what it does not own */\n   context: SemanticContextStoreContract;\n   journal: InteractionJournal;\n+  /** optional durable pin for the first interpretation of each observation */\n+  turnStore?: AcceptedTurnStore;\n   capabilities: CapabilitySemantics[];\n   workflow: WorkflowDefinition;\n   backend: PerceptionBackend;\n@@ -105,6 +115,10 @@ export interface ContinuingTurnReport {\n export interface ContinuingInteractionHost {\n   readonly context: SemanticContextStoreContract;\n   runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport>;\n+  /** Drive a typed kernel command (confirm/cancel/resume/revise/propose). */\n+  command(command: KernelCommand, now: string): Promise<TurnResult>;\n+  /** Resolve an UNKNOWN dispatch from its recorded receipt. */\n+  reconcile(effectId: EffectId, now: string): Promise<TurnResult>;\n   /**\n    * A non-language kernel input turn (UI focus/selection, world events).\n    * Language never goes through here; the perception path is `runTurn`.\n@@ -215,6 +229,15 @@ export function createContinuingInteractionHost(\n   config: ContinuingInteractionConfig,\n ): ContinuingInteractionHost {\n   let clockNow: string | undefined;\n+  const turnStore = config.turnStore ?? createInMemoryAcceptedTurnStore();\n+  // A continuing interaction has one discourse timeline. Serialize ingestion\n+  // and language turns so each frame is grounded in the previous turn's state.\n+  let turnQueue: Promise<void> = Promise.resolve();\n+  function enqueue<T>(work: () => Promise<T>): Promise<T> {\n+    const result = turnQueue.then(work);\n+    turnQueue = result.then(() => undefined, () => undefined);\n+    return result;\n+  }\n   const runtime = createRuntime({\n     ports: {\n       authorityFor: (ref) => {\n@@ -256,51 +279,117 @@ export function createContinuingInteractionHost(\n     context: config.context,\n     state: () => runtime.state(),\n \n-    async ingest(observation: Observation): Promise<TurnResult> {\n-      clockNow = observation.at;\n-      return runtime.ingest(observation);\n+    ingest(observation: Observation): Promise<TurnResult> {\n+      const stableObservation = structuredClone(observation);\n+      return enqueue(async () => {\n+        clockNow = stableObservation.at;\n+        return runtime.ingest(stableObservation);\n+      });\n     },\n \n-    async runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n-      clockNow = input.now;\n-      const state = runtime.state();\n-      const scopeId = state.scopeStack[state.scopeStack.length - 1];\n-      const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n-      const refs = input.refs ?? scopeRefs;\n-      await observe(refs);\n-\n-      const { frame } = await assembleFrame(config.context, {\n-        interactionId: config.interactionId,\n-        observationId: input.observationId,\n-        utterance: { text: input.utterance, source: \"text\" },\n-        interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n-        capabilities: config.capabilities,\n-        now: input.now,\n+    command(command: KernelCommand, now: string): Promise<TurnResult> {\n+      const stableCommand = structuredClone(command);\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.command(stableCommand);\n       });\n+    },\n \n-      const run = await runPerceptionWorkflow({\n-        backend: config.backend,\n-        frame,\n-        workflow: config.workflow,\n-        policy: config.policy,\n-        now: input.now,\n-        runId: `host:${config.interactionId}:${input.observationId}`,\n+    reconcile(effectId: EffectId, now: string): Promise<TurnResult> {\n+      return enqueue(async () => {\n+        clockNow = now;\n+        return runtime.reconcile(effectId);\n       });\n+    },\n \n-      const turn = semanticTurnFromRun(run, config.propositions(run, frame));\n-      const compiled = compileSemanticTurn(turn, {\n-        frame,\n-        commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n-      });\n+    runTurn(input: ContinuingTurnInput): Promise<ContinuingTurnReport> {\n+      const stableInput = structuredClone(input);\n+      return enqueue(async () => {\n+        const input = stableInput;\n+        const key = `${config.interactionId}:${input.observationId}`;\n+        const inputFingerprint = JSON.stringify({\n+          observationId: input.observationId,\n+          utterance: input.utterance,\n+          now: input.now,\n+          refs: input.refs === undefined\n+            ? \"<active-scope>\"\n+            : input.refs.map((ref) => ({\n+                namespace: ref.namespace,\n+                type: ref.type,\n+                id: ref.id,\n+                authority: ref.authority,\n+              })),\n+        });\n+        const pinned = turnStore.get(key);\n+        if (pinned && pinned.inputFingerprint !== inputFingerprint) {\n+          throw new Error(`observation identity ${input.observationId} was reused with different turn input`);\n+        }\n \n-      config.beforeCommands?.(frame);\n+        clockNow = input.now;\n+        let frame: ContextFrame;\n+        let run: WorkflowRun;\n+        let turn: SemanticTurn;\n+        let compiled: CompiledTurn;\n+        if (pinned) {\n+          ({ frame, run, turn, compiled } = pinned);\n+        } else {\n+          const state = runtime.state();\n+          const scopeId = state.scopeStack[state.scopeStack.length - 1];\n+          const scopeRefs = scopeId ? (state.scopes.get(scopeId)?.refs ?? []) : [];\n+          const refs = input.refs ?? scopeRefs;\n+          await observe(refs);\n \n-      const results: TurnResult[] = [];\n-      for (const command of compiled.commands) {\n-        results.push(await runtime.command(command));\n-      }\n+          ({ frame } = await assembleFrame(config.context, {\n+            interactionId: config.interactionId,\n+            observationId: input.observationId,\n+            utterance: { text: input.utterance, source: \"text\" },\n+            interaction: frameInteractionFor(runtime.state(), refs, descriptors()),\n+            capabilities: config.capabilities,\n+            now: input.now,\n+          }));\n+\n+          run = await runPerceptionWorkflow({\n+            backend: config.backend,\n+            frame,\n+            workflow: config.workflow,\n+            policy: config.policy,\n+            now: input.now,\n+            runId: `host:${config.interactionId}:${frame.id}`,\n+          });\n+\n+          turn = semanticTurnFromRun(run, config.propositions(run, frame));\n+          compiled = compileSemanticTurn(turn, {\n+            frame,\n+            commandIdFor: (_proposition, index) => `${config.interactionId}:${input.observationId}:${index}`,\n+          });\n+          const record: AcceptedTurnRecord = { key, inputFingerprint, frame, run, turn, compiled };\n+          // Pin the first interpretation before trace writes or any command dispatch.\n+          turnStore.record(record);\n+        }\n \n-      return { frame, run, turn, compiled, results, state: runtime.state() };\n+        persistDecisionTraces(config.context, run.traces);\n+        config.beforeCommands?.(frame);\n+\n+        const results: TurnResult[] = [];\n+        for (const command of compiled.commands) {\n+          results.push(await runtime.command(command));\n+        }\n+\n+        return { frame, run, turn, compiled, results, state: runtime.state() };\n+      });\n     },\n   };\n }\n+\n+function persistDecisionTraces(store: SemanticContextStoreContract, traces: DecisionTrace[]): void {\n+  for (const trace of traces) {\n+    const existing = store.decision(trace.id);\n+    if (existing) {\n+      if (JSON.stringify(existing) !== JSON.stringify(trace)) {\n+        throw new Error(`decision identity ${trace.id} was reused with different provenance`);\n+      }\n+      continue;\n+    }\n+    store.recordDecision(trace);\n+  }\n+}\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn apps/inspector/src/host.ts around line 121, verify expose recovery and confirmation through the continuing host against the baseline 5dc176f and corrective commit 927c9ea. The file host can reconstruct a confirmation gate or UNKNOWN effect, but its public API only offers ingestion and request-oriented semantic turns. The underlying runtime is private. Application callers cannot use the kernel\u2019s typed confirm or reconcile methods through this host. The host now exposes command and reconcile, using the same queue and clock as other operations. Retained tests restart before confirmation and after a lost invocation response; confirmation executes once and reconciliation resolves the recorded application without invoking again. This supplies typed controls. It does not implement an English confirmation/correction workflow. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. host.test.ts exercises both missing APIs through real file-backed restart; both fail as absent methods on the original PR."
    },
    {
      "id": "F81",
      "title": "Apply the same canonical context values before and after restart",
      "file": "packages/context-file/src/index.ts",
      "lines": "132",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "data-integrity",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "PR #9 validates and appends context operations, then applies their original input objects in memory. Replay applies the decoder\u2019s normalized shape instead. An entity with an extra runtime property therefore has different content before and after reopening. Probability decoding also drops an own __proto__ key, and the writer accepts sparse or symbol-keyed input that JSON cannot faithfully preserve.",
        "The writer now decodes the encoded operation before appending and applies that canonical value immediately. Numeric records preserve own keys; unrepresentable values and a second format header are rejected. These changes reuse the existing codec rather than introduce a second persistence model."
      ],
      "tests": "context-file/index.test.ts has four failing comparisons on PR #9, covering canonical parity, probability ownership, invalid input and repeated headers.",
      "suggestedDiff": "diff --git a/packages/context-file/src/index.ts b/packages/context-file/src/index.ts\nindex 9d85a33..2277b30 100644\n--- a/packages/context-file/src/index.ts\n+++ b/packages/context-file/src/index.ts\n@@ -7,6 +7,7 @@ import {\n   CONTEXT_LOG_FORMAT,\n   assertPlainData,\n   decodeContextLogOp,\n+  encodeContextLogOp,\n   type ContextLogOp,\n } from \"./codec\";\n \n@@ -113,6 +114,9 @@ export function createFileContextStore(\n     throw new Error(`semantic store ${path} does not start with a format record`);\n   }\n   for (const [index, op] of existing.entries()) {\n+    if (index > 0 && op.op === \"format\") {\n+      throw new Error(`semantic store ${path}: unexpected format record at operation ${index + 1}`);\n+    }\n     try {\n       applyContextLogOp(inner, op);\n     } catch (error) {\n@@ -125,41 +129,46 @@ export function createFileContextStore(\n   }\n \n   /** Validate representability, then make the operation durable, then apply. */\n-  const record = (op: ContextLogOp): void => {\n+  const record = <T extends ContextLogOp>(op: T): T => {\n     assertPlainData(op, \"operation\");\n-    log.append(op);\n+    // Replay applies the decoder's normalized shape. Apply that same shape\n+    // now, so unknown runtime fields and omitted optional values cannot make\n+    // live state differ from a reopened store.\n+    const canonical = decodeContextLogOp(JSON.parse(encodeContextLogOp(op))) as T;\n+    log.append(canonical);\n+    return canonical;\n   };\n \n   return {\n     version: () => inner.version(),\n \n     putEntity(entity) {\n-      record({ op: \"put-entity\", record: entity });\n-      inner.putEntity(entity);\n+      const op = record({ op: \"put-entity\", record: entity });\n+      inner.putEntity(op.record);\n     },\n     recordObservation(input) {\n-      record({ op: \"record-observation\", input });\n-      return inner.recordObservation(input);\n+      const op = record({ op: \"record-observation\", input });\n+      return inner.recordObservation(op.input);\n     },\n     addRelation(input) {\n-      record({ op: \"add-relation\", input });\n-      return inner.addRelation(input);\n+      const op = record({ op: \"add-relation\", input });\n+      return inner.addRelation(op.input);\n     },\n     recordEvent(input) {\n-      record({ op: \"record-event\", input });\n-      return inner.recordEvent(input);\n+      const op = record({ op: \"record-event\", input });\n+      return inner.recordEvent(op.input);\n     },\n     recordAssertion(input) {\n-      record({ op: \"record-assertion\", input });\n-      return inner.recordAssertion(input);\n+      const op = record({ op: \"record-assertion\", input });\n+      return inner.recordAssertion(op.input);\n     },\n     addAnnotation(input) {\n-      record({ op: \"add-annotation\", input });\n-      return inner.addAnnotation(input);\n+      const op = record({ op: \"add-annotation\", input });\n+      return inner.addAnnotation(op.input);\n     },\n     addBinding(input) {\n-      record({ op: \"add-binding\", input });\n-      return inner.addBinding(input);\n+      const op = record({ op: \"add-binding\", input });\n+      return inner.addBinding(op.input);\n     },\n     recordDecision(trace) {\n       // Pre-check so a rejected duplicate never becomes a durable record that\n@@ -169,8 +178,8 @@ export function createFileContextStore(\n           `duplicate decision id ${trace.id}: decision provenance is never overwritten`,\n         );\n       }\n-      record({ op: \"record-decision\", trace });\n-      return inner.recordDecision(trace);\n+      const op = record({ op: \"record-decision\", trace });\n+      return inner.recordDecision(op.trace);\n     },\n     recordFrame(frame) {\n       const existingFrame = inner.frame(frame.id);\n@@ -180,27 +189,28 @@ export function createFileContextStore(\n           `duplicate frame id ${frame.id} with different content: frames are immutable`,\n         );\n       }\n-      record({ op: \"record-frame\", frame });\n-      inner.recordFrame(frame);\n+      const op = record({ op: \"record-frame\", frame });\n+      inner.recordFrame(op.frame);\n     },\n     upsertSalience(input) {\n-      record({ op: \"upsert-salience\", record: input });\n-      inner.upsertSalience(input);\n+      const op = record({ op: \"upsert-salience\", record: input });\n+      inner.upsertSalience(op.record);\n     },\n     invalidate(ids, at) {\n-      record({ op: \"invalidate\", ids, at });\n-      return inner.invalidate(ids, at);\n+      const op = record({ op: \"invalidate\", ids, at });\n+      return inner.invalidate(op.ids, op.at);\n     },\n     invalidateDependents(ids, at) {\n-      record({ op: \"invalidate-dependents\", ids, at });\n-      return inner.invalidateDependents(ids, at);\n+      const op = record({ op: \"invalidate-dependents\", ids, at });\n+      return inner.invalidateDependents(op.ids, op.at);\n     },\n     currentGeneration(ref) {\n       // Minting a generation is a state transition: record it exactly once so\n       // replay reproduces the same generation ids and dependency links.\n       const existingGeneration = inner.peekGeneration(ref);\n       if (existingGeneration === undefined) {\n-        record({ op: \"ensure-generation\", ref });\n+        const op = record({ op: \"ensure-generation\", ref });\n+        return inner.currentGeneration(op.ref);\n       }\n       return inner.currentGeneration(ref);\n     },\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn packages/context-file/src/index.ts around line 132, verify apply the same canonical context values before and after restart against the baseline 5dc176f and corrective commit 927c9ea. PR #9 validates and appends context operations, then applies their original input objects in memory. Replay applies the decoder\u2019s normalized shape instead. An entity with an extra runtime property therefore has different content before and after reopening. Probability decoding also drops an own __proto__ key, and the writer accepts sparse or symbol-keyed input that JSON cannot faithfully preserve. The writer now decodes the encoded operation before appending and applies that canonical value immediately. Numeric records preserve own keys; unrepresentable values and a second format header are rejected. These changes reuse the existing codec rather than introduce a second persistence model. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. context-file/index.test.ts has four failing comparisons on PR #9, covering canonical parity, probability ownership, invalid input and repeated headers."
    },
    {
      "id": "F82",
      "title": "Reject invalid UTF-8 in completed log records",
      "file": "packages/file-log/src/index.ts",
      "lines": "81",
      "severity": "minor",
      "pBadge": "P2",
      "type": "logic",
      "section": "actionable",
      "category": "data-integrity",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "The original file reader decodes bytes with replacement semantics. Invalid UTF-8 inside a completed JSON string can become the replacement character while the record remains syntactically valid. Evidence has then changed without a corruption error.",
        "The reader now uses fatal UTF-8 decoding and physical byte offsets. Completed corrupt records fail; an unterminated torn fragment is repaired at the last valid append boundary. The completed-record test fails on PR #9. The torn-fragment regression also passes on the original code and is a preservation control, not evidence of a second defect."
      ],
      "tests": "file-log/index.test.ts tests completed UTF-8 corruption and torn-tail repair; all 41 journal regressions remain green.",
      "suggestedDiff": "diff --git a/packages/file-log/src/index.ts b/packages/file-log/src/index.ts\nindex 594d04d..c55cbcb 100644\n--- a/packages/file-log/src/index.ts\n+++ b/packages/file-log/src/index.ts\n@@ -11,6 +11,8 @@ import {\n } from \"node:fs\";\n import { dirname } from \"node:path\";\n \n+const strictUtf8 = new TextDecoder(\"utf-8\", { fatal: true });\n+\n /**\n  * Generic append-only JSONL file log.\n  *\n@@ -76,22 +78,45 @@ function parseLog<T>(path: string, decode: (value: unknown) => T, label: string)\n   if (stats.isSymbolicLink() || !stats.isFile()) {\n     throw new Error(`${label} path ${path} is not a regular file`);\n   }\n-  const text = readFileSync(path, \"utf8\");\n-  const endsWithNewline = text.endsWith(\"\\n\");\n-  const totalBytes = Buffer.byteLength(text, \"utf8\");\n-  const lines = text.split(\"\\n\");\n+  const bytes = readFileSync(path);\n+  const endsWithNewline = bytes.length > 0 && bytes[bytes.length - 1] === 0x0a;\n+  const totalBytes = bytes.length;\n+  const lines: Buffer[] = [];\n+  let lineStart = 0;\n+  for (let index = 0; index < bytes.length; index += 1) {\n+    if (bytes[index] === 0x0a) {\n+      lines.push(bytes.subarray(lineStart, index));\n+      lineStart = index + 1;\n+    }\n+  }\n+  lines.push(bytes.subarray(lineStart));\n   const lastIndex = lines.length - 1;\n   let lastNonEmpty = -1;\n-  lines.forEach((line, index) => {\n-    if (line.trim()) lastNonEmpty = index;\n+  const decodedLines: Array<string | undefined> = lines.map((line, index) => {\n+    try {\n+      const decoded = strictUtf8.decode(line);\n+      if (decoded.trim()) lastNonEmpty = index;\n+      return decoded;\n+    } catch (error) {\n+      if (index === lastIndex && !endsWithNewline) {\n+        lastNonEmpty = index;\n+        return undefined;\n+      }\n+      throw new Error(`corrupt ${label} line ${index + 1} in ${path}: invalid UTF-8 (${String(error)})`);\n+    }\n   });\n \n   const values: T[] = [];\n   let offset = 0;\n   let lastRecordHadNewline = false;\n-  for (const [index, raw] of lines.entries()) {\n+  for (const [index, rawBytes] of lines.entries()) {\n     const isLastSegment = index === lastIndex;\n     const hasNewline = !(isLastSegment && !endsWithNewline);\n+    const raw = decodedLines[index];\n+    if (raw === undefined) {\n+      // An incomplete UTF-8 sequence at the tail is a torn append.\n+      return { values, repair: { offset, needsSeparator: false } };\n+    }\n     const trimmed = raw.trim();\n     if (!trimmed) {\n       // only the final segment produced by a trailing newline may be empty\n@@ -101,7 +126,7 @@ function parseLog<T>(path: string, decode: (value: unknown) => T, label: string)\n     try {\n       const parsed: unknown = JSON.parse(trimmed);\n       values.push(decode(parsed));\n-      offset += Buffer.byteLength(raw, \"utf8\") + (hasNewline ? 1 : 0);\n+      offset += rawBytes.length + (hasNewline ? 1 : 0);\n       lastRecordHadNewline = hasNewline;\n     } catch (error) {\n       // Only a torn JSON fragment at the final line is a crash artifact;\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn packages/file-log/src/index.ts around line 81, verify reject invalid utf-8 in completed log records against the baseline 5dc176f and corrective commit 927c9ea. The original file reader decodes bytes with replacement semantics. Invalid UTF-8 inside a completed JSON string can become the replacement character while the record remains syntactically valid. Evidence has then changed without a corruption error. The reader now uses fatal UTF-8 decoding and physical byte offsets. Completed corrupt records fail; an unterminated torn fragment is repaired at the last valid append boundary. The completed-record test fails on PR #9. The torn-fragment regression also passes on the original code and is a preservation control, not evidence of a second defect. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. file-log/index.test.ts tests completed UTF-8 corruption and torn-tail repair; all 41 journal regressions remain green."
    },
    {
      "id": "F83",
      "title": "Evaluate applied outcomes rather than effect IDs",
      "file": "apps/inspector/src/heldout.ts",
      "lines": "315",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "correctness",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "The original evaluation can pass after an executor returns EXECUTION_ERROR because its command and effect-count checks still match. Its fixture client also searches ahead for a convenient matching question set, masking a wrong call sequence. It has no active injected-client option for running the same cases against a real provider.",
        "The corrected evaluation requires successful effects and actual expected target invocation, checks no mutation on abstention, consumes the next fixture strictly, accepts an injected client, and counts actual calls instead of cached run metadata. Each evaluation gets fresh files. Failures remain visible as case checks. The unchanged fixture suite remains a pipeline test rather than semantic accuracy evidence."
      ],
      "tests": "heldout-review.test.ts covers a failed executor, transcript skipping, injection, redelivery call counts and independent evaluation directories.",
      "suggestedDiff": "diff --git a/apps/inspector/src/heldout.ts b/apps/inspector/src/heldout.ts\nindex 1f9d717..3a97d0c 100644\n--- a/apps/inspector/src/heldout.ts\n+++ b/apps/inspector/src/heldout.ts\n@@ -1,10 +1,11 @@\n-import { mkdtempSync, rmSync } from \"node:fs\";\n+import { mkdirSync, mkdtempSync, rmSync } from \"node:fs\";\n import { tmpdir } from \"node:os\";\n import { join } from \"node:path\";\n import type { CapabilityDescriptor, EntityRef } from \"@circe/core\";\n import type { CapabilitySemantics, ContextFrame } from \"@circe/context\";\n import {\n   ACCEPTANCE_POLICY_V1,\n+  choiceOf,\n   type AcceptancePolicy,\n   type DecisionSpec,\n   type PerceptionStatus,\n@@ -25,15 +26,15 @@ import { acceptedChoice, refForChoice } from \"./scenario\";\n /**\n  * Held-out interaction evaluation.\n  *\n- * Cases are recorded conversations with a reference model: each backend call\n- * is answered from a transcript matched by the exact decision ids asked, and\n- * the harness scores what the whole product did with that answer \u2014 retrieval,\n+ * In fixture mode each backend call is answered from a supplied script matched\n+ * by the exact decision ids asked. The evaluation checks retrieval,\n  * frame assembly, policy, accepted turns, compiled commands, kernel effects\n  * and restart recovery.\n  *\n  * This measures the pipeline, not language understanding. The same\n  * `JevDecisionClient` seam accepts a live transport; replacing the recorded\n- * client with a live one is the only change a model accuracy study needs. No\n+ * client with a live one permits actual model runs. A representative corpus and\n+ * outcome calibration are separate work. No\n  * number in this harness is a claim about model quality.\n  */\n \n@@ -80,12 +81,13 @@ export interface HeldOutCase {\n   refs: EntityRef[];\n   descriptor: CapabilityDescriptor;\n   capability: CapabilitySemantics;\n+  actSpec?: DecisionSpec;\n   referentSpec: DecisionSpec;\n   operationSpec: DecisionSpec;\n   policy?: AcceptancePolicy;\n   turns: HeldOutTurn[];\n   /**\n-   * Recorded reference-model answers, consumed in call order. Each entry must\n+   * Hand-written fixture answers, consumed in call order. Each entry must\n    * match the exact decision ids of the call it answers.\n    */\n   scripts: ScriptedTurn[];\n@@ -130,22 +132,21 @@ export interface HeldOutEvaluation {\n  * order. A live host would replace this with a transport-backed client.\n  */\n export function createSequencedReferenceClient(scripts: ScriptedTurn[]): JevDecisionClient {\n+  const transcript = structuredClone(scripts);\n   let cursor = 0;\n   return {\n     id: \"held-out-reference\",\n     async complete(request: JevDecisionRequest): Promise<unknown> {\n       const asked = request.decisions.map((decision) => decision.id).sort();\n-      for (let index = cursor; index < scripts.length; index += 1) {\n-        const script = scripts[index]!;\n-        const wanted = [...script.decisions].sort();\n-        if (wanted.length === asked.length && wanted.every((id, position) => id === asked[position])) {\n-          cursor = index + 1;\n-          return {\n-            status: script.status ?? \"RESOLVED\",\n-            answers: structuredClone(script.answers),\n-            latencyMs: script.latencyMs ?? 0,\n-          };\n-        }\n+      const script = transcript[cursor];\n+      const wanted = script ? [...script.decisions].sort() : [];\n+      if (script && wanted.length === asked.length && wanted.every((id, position) => id === asked[position])) {\n+        cursor += 1;\n+        return {\n+          status: script.status ?? \"RESOLVED\",\n+          answers: structuredClone(script.answers),\n+          latencyMs: script.latencyMs ?? 0,\n+        };\n       }\n       throw new Error(\n         `no held-out scripted answer for decisions [${asked.join(\", \")}] at call ${cursor + 1}`,\n@@ -162,8 +163,10 @@ export function createSequencedReferenceClient(scripts: ScriptedTurn[]): JevDeci\n export function selectAndActPropositions(\n   referentSpec: DecisionSpec,\n   operationSpec: DecisionSpec,\n+  actSpec?: DecisionSpec,\n ): (run: WorkflowRun, frame: ContextFrame) => SemanticProposition[] {\n   return (run, frame) => {\n+    if (actSpec && acceptedChoice(run, actSpec.id) !== \"request\") return [];\n     const target = acceptedChoice(run, referentSpec.id);\n     const capability = acceptedChoice(run, operationSpec.id);\n     if (!target || target === \"none\" || !capability || capability === \"none\") return [];\n@@ -179,15 +182,24 @@ export function selectAndActPropositions(\n         refs: [ref],\n         polarity: \"include\",\n         capability,\n-        decisions: [referentTrace.id, operationTrace.id],\n+        decisions: [referentTrace.id, operationTrace.id,\n+          ...(actSpec ? run.traces.filter((trace) => trace.specId === actSpec.id).map((trace) => trace.id) : [])],\n       },\n     ];\n   };\n }\n \n+export interface HeldOutEvaluationOptions {\n+  /** Parent for preserved per-case run directories; every run gets a fresh directory. */\n+  directory?: string;\n+  /** Omit for fixture playback; supply a real client for a live model evaluation. */\n+  clientForCase?: (scenario: HeldOutCase) => JevDecisionClient;\n+  model?: string;\n+}\n+\n export async function runHeldOutEvaluation(\n   cases: HeldOutCase[],\n-  options: { directory?: string } = {},\n+  options: HeldOutEvaluationOptions = {},\n ): Promise<HeldOutEvaluation> {\n   const reports: HeldOutCaseReport[] = [];\n   const metrics: HeldOutMetrics = {\n@@ -204,23 +216,30 @@ export async function runHeldOutEvaluation(\n   for (const scenario of cases) {\n     metrics.cases += 1;\n     const ownedDirectory = options.directory === undefined;\n-    const directory =\n-      options.directory ?? mkdtempSync(join(tmpdir(), `circe-heldout-${scenario.id}-`));\n+    if (options.directory !== undefined) mkdirSync(options.directory, { recursive: true });\n+    const directory = mkdtempSync(join(options.directory ?? tmpdir(), \"circe-heldout-\"));\n     const paths = {\n-      journalPath: join(directory, `${scenario.id}.journal.jsonl`),\n-      contextPath: join(directory, `${scenario.id}.context.jsonl`),\n+      journalPath: join(directory, \"journal.jsonl\"),\n+      contextPath: join(directory, \"context.jsonl\"),\n     };\n     const world = createDemoWorld(scenario.entities, scenario.descriptor);\n-    const client = createSequencedReferenceClient(scenario.scripts);\n-    const backend = createJevBackend({ client, model: \"held-out-reference\" });\n+    const client = options.clientForCase?.(scenario) ?? createSequencedReferenceClient(scenario.scripts);\n+    const backend = createJevBackend({\n+      client: { id: client.id, async complete(request) {\n+        metrics.backendCalls += 1;\n+        return client.complete(request);\n+      } },\n+      model: options.model ?? \"held-out-reference\",\n+    });\n     const workflow: WorkflowDefinition = {\n       id: `${scenario.id}.v1`,\n       stages: [\n-        { id: \"referent\", specs: () => [scenario.referentSpec] },\n+        ...(scenario.actSpec ? [{ id: \"act\", specs: () => [scenario.actSpec!] }] : []),\n+        { id: \"referent\", specs: (state) => !scenario.actSpec || choiceOf(state, scenario.actSpec.id) === \"request\" ? [scenario.referentSpec] : [] },\n         { id: \"operation\", specs: () => [scenario.operationSpec] },\n       ],\n     };\n-    const propositions = selectAndActPropositions(scenario.referentSpec, scenario.operationSpec);\n+    const propositions = selectAndActPropositions(scenario.referentSpec, scenario.operationSpec, scenario.actSpec);\n     const config = {\n       interactionId: `interaction:${scenario.id}`,\n       capabilities: [scenario.capability],\n@@ -232,11 +251,12 @@ export async function runHeldOutEvaluation(\n       executors: world.executors,\n     };\n \n-    let host = createFileContinuingInteractionHost({ ...config, ...paths });\n+    let host: ReturnType<typeof createFileContinuingInteractionHost> | undefined;\n     const checks: HeldOutCheck[] = [];\n     const commands: HeldOutCaseReport[\"commands\"] = [];\n     const effectIds: string[] = [];\n     let turnIndex = 0;\n+    const delivered = new Set<string>();\n \n     const check = (name: string, ok: boolean, detail?: string): void => {\n       checks.push({ turn: turnIndex, name, ok, ...(detail !== undefined ? { detail } : {}) });\n@@ -246,6 +266,7 @@ export async function runHeldOutEvaluation(\n     };\n \n     try {\n+      host = createFileContinuingInteractionHost({ ...config, ...paths });\n       for (const turn of scenario.turns) {\n         turnIndex += 1;\n         metrics.turns += 1;\n@@ -260,13 +281,13 @@ export async function runHeldOutEvaluation(\n             expectEqual(\"ui scope\", result.scope.map((ref) => ref.id), turn.expect.scope);\n           }\n         } else {\n+          const invokedBefore = world.invoked.length;\n           const report = await host.runTurn({\n             observationId: turn.observationId,\n             utterance: turn.utterance,\n             now: turn.now,\n             ...(turn.refs !== undefined ? { refs: turn.refs } : {}),\n           });\n-          metrics.backendCalls += report.run.calls;\n           metrics.acceptedDecisions += report.run.state.accepted.size;\n           const turnCommands = report.compiled.commands.map((command) => ({\n             capability: command.capability,\n@@ -288,8 +309,15 @@ export async function runHeldOutEvaluation(\n           }\n           if (expect.command === null) {\n             expectEqual(\"no compiled command\", turnCommands.length, 0);\n+            expectEqual(\"no applied mutation\", world.invoked.length, invokedBefore);\n           } else if (expect.command !== undefined) {\n             expectEqual(\"compiled command\", turnCommands, [expect.command]);\n+            check(\"execution succeeded\", report.results.length === turnCommands.length &&\n+              report.results.every((result) => !result.errors?.length && result.effects.length > 0 &&\n+                result.effects.every((effect) => effect.status === \"SUCCEEDED\")),\n+              \"Every expected command must produce a successful effect, not merely an effect id.\");\n+            expectEqual(\"applied targets\", world.invoked.slice(invokedBefore).map((ref) => ref.id),\n+              delivered.has(turn.observationId) ? [] : expect.command.targets);\n           }\n           if (expect.effectCount !== undefined) {\n             expectEqual(\"cumulative effects\", effectIds.length, expect.effectCount);\n@@ -308,6 +336,7 @@ export async function runHeldOutEvaluation(\n               `expected at least ${expect.recentReferentsAtLeast}, got ${report.frame.interaction.recentReferents.length}`,\n             );\n           }\n+          delivered.add(turn.observationId);\n           if (turnCommands.length === 0) {\n             // A run that failed validation is not an abstention; it produced\n             // no binding because the answer was unusable, not because the\n@@ -321,9 +350,12 @@ export async function runHeldOutEvaluation(\n           host = createFileContinuingInteractionHost({ ...config, ...paths });\n         }\n       }\n+    } catch (error) {\n+      check(\"case completed\", false, error instanceof Error ? error.message : \"Case threw an unknown error\");\n     } finally {\n-      host.close();\n-      if (ownedDirectory) rmSync(directory, { recursive: true, force: true });\n+      try { host?.close(); }\n+      catch (error) { check(\"stores closed\", false, error instanceof Error ? error.message : \"Close failed\"); }\n+      finally { if (ownedDirectory) rmSync(directory, { recursive: true, force: true }); }\n     }\n \n     const failed = checks.filter((entry) => !entry.ok);\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn apps/inspector/src/heldout.ts around line 315, verify evaluate applied outcomes rather than effect ids against the baseline 5dc176f and corrective commit 927c9ea. The original evaluation can pass after an executor returns EXECUTION_ERROR because its command and effect-count checks still match. Its fixture client also searches ahead for a convenient matching question set, masking a wrong call sequence. It has no active injected-client option for running the same cases against a real provider. The corrected evaluation requires successful effects and actual expected target invocation, checks no mutation on abstention, consumes the next fixture strictly, accepts an injected client, and counts actual calls instead of cached run metadata. Each evaluation gets fresh files. Failures remain visible as case checks. The unchanged fixture suite remains a pipeline test rather than semantic accuracy evidence. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. heldout-review.test.ts covers a failed executor, transcript skipping, injection, redelivery call counts and independent evaluation directories."
    },
    {
      "id": "F84",
      "title": "Provide a runnable live bounded-decision transport",
      "file": "packages/perception-jev/src/typesafe-client.ts",
      "lines": "68",
      "severity": "major",
      "pBadge": "P1",
      "type": "logic",
      "section": "actionable",
      "category": "correctness",
      "status": "fixed",
      "fixedAt": "927c9ea409b3c6c6133ca5e3dc00af2b64d5c97f",
      "reviewRound": 1,
      "followUp": false,
      "bodyParagraphs": [
        "PR #9 explicitly leaves the live transport to a future host. The injected interface and hand-written answers therefore do not demonstrate the original product requirement of language passing through real semantic decisions into safe execution.",
        "The corrective branch adds a native-fetch TypeSafe System One client and eval:live entry point. It maps bounded questions, validates answer keys and distributions, handles HTTP errors and timeouts, and preserves returned model metadata. Its live corpus includes a request/query decision so a query cannot become a mutation through referent selection alone.",
        "This closes the missing code path only. Mock HTTP tests and mocked live-workflow controls pass, but no API key or account-supported model is configured. No real-model call or accuracy result exists. That remains an external validation blocker and is not represented as completed product behavior."
      ],
      "tests": "Seven additional Jev transport tests, two mocked live-workflow controls, and an explicit no-credentials exit check. Official contract: https://api.typesafe.ai/openapi.json.",
      "suggestedDiff": "diff --git a/packages/perception-jev/src/typesafe-client.ts b/packages/perception-jev/src/typesafe-client.ts\nnew file mode 100644\nindex 0000000..f5aab18\n--- /dev/null\n+++ b/packages/perception-jev/src/typesafe-client.ts\n@@ -0,0 +1,126 @@\n+import type { DecisionSpec } from \"@circe/perception\";\n+import type { JevDecisionClient, JevDecisionRequest } from \"./backend\";\n+\n+const DEFAULT_BASE_URL = \"https://api.typesafe.ai\";\n+const DEFAULT_TIMEOUT_MS = 30_000;\n+\n+export interface TypeSafeJevClientOptions {\n+  apiKey?: string;\n+  apiKeyEnv?: string;\n+  baseUrl?: string;\n+  timeoutMs?: number;\n+  fetch?: typeof globalThis.fetch;\n+}\n+\n+type TypeSafeQuestion =\n+  | { type: \"noul\"; instructions: string; criteria?: { true?: string; false?: string } }\n+  | { type: \"choice\"; instructions: string; criteria: Record<string, string> }\n+  | { type: \"score\"; instructions: string; criteria: string[] };\n+\n+function isRecord(value: unknown): value is Record<string, unknown> {\n+  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n+}\n+\n+function questionFor(spec: DecisionSpec): TypeSafeQuestion {\n+  switch (spec.kind) {\n+    case \"binary\":\n+      return { type: \"noul\", instructions: spec.question, ...(spec.criteria ? { criteria: spec.criteria } : {}) };\n+    case \"choice\":\n+      return {\n+        type: \"choice\",\n+        instructions: spec.question,\n+        criteria: Object.fromEntries(spec.options.map(({ id, description }) => [id, description ?? id])),\n+      };\n+    case \"score\":\n+      return { type: \"score\", instructions: spec.question, criteria: [...spec.levels] };\n+  }\n+}\n+\n+function answerFor(spec: DecisionSpec, raw: unknown): Record<string, unknown> {\n+  if (!isRecord(raw)) throw new Error(`TypeSafe returned a malformed answer for decision ${spec.id}`);\n+  if (spec.kind === \"binary\" && raw.type === \"noul\" && typeof raw.noul === \"number\") {\n+    return { id: spec.id, kind: \"binary\", probability: raw.noul };\n+  }\n+  if (spec.kind === \"choice\" && raw.type === \"choice\" && typeof raw.choice === \"string\" && isRecord(raw.probabilities) && typeof raw.confidence === \"number\") {\n+    return { id: spec.id, kind: \"choice\", choice: raw.choice, probabilities: raw.probabilities, confidence: raw.confidence };\n+  }\n+  if (spec.kind === \"score\" && raw.type === \"score\" && typeof raw.score === \"number\" && isRecord(raw.probabilities) && typeof raw.confidence === \"number\") {\n+    const providerProbabilities = raw.probabilities;\n+    const expectedLevels = spec.levels.map((_, index) => String(index));\n+    const unexpectedLevel = Object.keys(providerProbabilities).find((level) => !expectedLevels.includes(level));\n+    if (unexpectedLevel !== undefined) {\n+      throw new Error(`TypeSafe returned unexpected score probability level ${unexpectedLevel} for decision ${spec.id}`);\n+    }\n+    const probabilities = expectedLevels.map((level) => providerProbabilities[level]);\n+    if (probabilities.some((probability) => typeof probability !== \"number\")) {\n+      throw new Error(`TypeSafe omitted a score probability level for decision ${spec.id}`);\n+    }\n+    return { id: spec.id, kind: \"score\", score: raw.score, probabilities, confidence: raw.confidence };\n+  }\n+  throw new Error(`TypeSafe answer type did not match decision ${spec.id}`);\n+}\n+\n+/**\n+ * Native-fetch adapter for TypeSafe's documented System One HTTP API.\n+ * Credentials are read only when complete() runs; callers may supply an\n+ * explicit key for managed hosts and tests.\n+ */\n+export function createTypeSafeJevClient(options: TypeSafeJevClientOptions = {}): JevDecisionClient {\n+  const fetchImpl = options.fetch ?? globalThis.fetch;\n+  const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n+  const keyName = options.apiKeyEnv ?? \"TYPESAFE_API_KEY\";\n+  return {\n+    id: \"typesafe-jev\",\n+    async complete(request: JevDecisionRequest): Promise<unknown> {\n+      const apiKey = options.apiKey ?? process.env[keyName];\n+      if (!apiKey) throw new Error(`TypeSafe API key is required (set ${keyName})`);\n+      const questions = Object.fromEntries(request.decisions.map((spec) => [spec.id, questionFor(spec)]));\n+      if (Object.keys(questions).length !== request.decisions.length) {\n+        throw new Error(\"TypeSafe request contains duplicate decision ids\");\n+      }\n+      const startedAt = Date.now();\n+      const controller = new AbortController();\n+      const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n+      try {\n+        const response = await fetchImpl(`${baseUrl}/v1/systemone`, {\n+          method: \"POST\",\n+          headers: { authorization: `Bearer ${apiKey}`, \"content-type\": \"application/json\" },\n+          body: JSON.stringify({\n+            model: request.model,\n+            state: {\n+              utterance: request.context.utterance,\n+              interaction: request.context.interaction,\n+              candidates: request.context.candidates,\n+              capabilities: request.context.capabilities,\n+              coverage: request.coverage,\n+            },\n+            questions,\n+          }),\n+          signal: controller.signal,\n+        });\n+        if (!response.ok) throw new Error(`TypeSafe request failed with HTTP ${response.status}`);\n+        const payload: unknown = await response.json();\n+        if (!isRecord(payload) || !isRecord(payload.answers)) throw new Error(\"TypeSafe returned a malformed response\");\n+        const providerAnswers = payload.answers;\n+        const askedIds = new Set(request.decisions.map((spec) => spec.id));\n+        const unexpectedId = Object.keys(providerAnswers).find((id) => !askedIds.has(id));\n+        if (unexpectedId !== undefined) throw new Error(`TypeSafe returned unasked answer for decision ${unexpectedId}`);\n+        const answers = request.decisions.map((spec) => {\n+          if (!Object.hasOwn(providerAnswers, spec.id)) throw new Error(`TypeSafe omitted answer for decision ${spec.id}`);\n+          return answerFor(spec, providerAnswers[spec.id]);\n+        });\n+        return {\n+          status: \"RESOLVED\",\n+          answers,\n+          ...(typeof payload.model === \"string\" ? { model: payload.model } : {}),\n+          latencyMs: Date.now() - startedAt,\n+        };\n+      } catch (error) {\n+        if (controller.signal.aborted) throw new Error(\"TypeSafe request timed out\");\n+        throw error;\n+      } finally {\n+        clearTimeout(timeout);\n+      }\n+    },\n+  };\n+}\n",
      "prompt": "Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.\n\nIn packages/perception-jev/src/typesafe-client.ts around line 68, verify provide a runnable live bounded-decision transport against the baseline 5dc176f and corrective commit 927c9ea. PR #9 explicitly leaves the live transport to a future host. The injected interface and hand-written answers therefore do not demonstrate the original product requirement of language passing through real semantic decisions into safe execution. The corrective branch adds a native-fetch TypeSafe System One client and eval:live entry point. It maps bounded questions, validates answer keys and distributions, handles HTTP errors and timeouts, and preserves returned model metadata. Its live corpus includes a request/query decision so a query cannot become a mutation through referent selection alone. Retain the passing controls and confirm the relevant regression fails for the intended reason on the baseline. Seven additional Jev transport tests, two mocked live-workflow controls, and an explicit no-credentials exit check. Official contract: https://api.typesafe.ai/openapi.json."
    }
  ],
  "roundDeltas": [],
  "architecture": [
    "PR #9 addresses the previous audit\u2019s largest structural gap: semantic context now survives restart alongside kernel state, and a continuing host rebuilds discourse instead of starting every call empty. PR #8 is already in its base, and all 147 earlier independent review checks still pass. This review did not duplicate those earlier fixes.",
    "The remaining defects are integration ownership problems. The host must own observation identity, the decision evidence preceding execution, and serialization of calls that share a clock and discourse. Leaving these responsibilities to individual callers would recreate the same alternate-entry-point drift seen in the earlier PR stack.",
    "The third accepted-turn log is justified because a semantic abstention creates no kernel command to deduplicate. It records the first interpretation before consequences. The kernel still owns authorization, effects and reconciliation; the context store still owns evidence. The files are ordered append boundaries, not an atomic multi-file transaction. This distinction is documented and the pre-dispatch cut is tested.",
    "The corrective change adds no external runtime dependency and does not rewrite the kernel. Most additions are regression tests, the live HTTP mapping, and a small turn snapshot codec. That is proportionate to the failures found. I would keep these boundaries and resist adding a general orchestration framework. The append-only JSONL adapters still read whole logs and lack compaction, so long-lived scale remains unproven."
  ],
  "limitations": [
    {
      "area": "Live semantic evidence is blocked",
      "detail": "No TYPESAFE_API_KEY or TYPESAFE_MODEL is configured. The live command exits before making a call. Configure them in /home/abstergo/.config/circe/typesafe.env outside Git to enable the next verification. Do not paste the secret into the task. The HTTP mapping was checked against TypeSafe\u2019s official OpenAPI document, but credentials and a real response are still needed."
    },
    {
      "area": "The language product is not finished",
      "detail": "Typed confirmation, revision, cancellation, resumption and reconciliation are available. The current request-oriented demo does not supply a general natural-language control or query-response workflow. The five synthetic live cases exercise a narrow slice. They are not a representative interpretation benchmark, and thresholds are not calibrated against observed outcomes."
    },
    {
      "area": "Durable storage is not automatic semantic learning",
      "detail": "The context adapter can persist annotations, assertions and phrase bindings; the host persists authority observations, frames, traces and interpreted turns. Applications still need to decide which accepted semantic results become durable beliefs or bindings. Persisted decision traces alone do not establish that the system learns reusable semantic knowledge."
    },
    {
      "area": "Storage operating contract remains limited",
      "detail": "One process owns each store path. Multi-process locking, migrations beyond current format checks, retention and compaction are not implemented. No performance claim for unbounded histories is supported by this review."
    }
  ],
  "verification": {
    "baselineTests": 438,
    "finalTests": 466,
    "externalChecks": 147,
    "baselineComparison": {
      "failed": 17,
      "passed": 23
    },
    "typecheck": "pass",
    "boundaries": "pass",
    "fixtureCases": 5,
    "live": "not run: missing API credentials/model"
  },
  "history": [
    {
      "round": 1,
      "at": "2026-09-24T03:40:33.371778+00:00",
      "note": "Review and corrective pass; no live accuracy claim."
    }
  ],
  "syncMeta": {
    "instructions": "Preserve this baseline and original finding bodies on re-review. Append supported findings. No numeric confidence scores."
  }
}
