# PR #1: approve the scoped safety pass

**The round-3 fixes hold. I would approve PR #1 at `ad32c48`.** No new blocking findings emerged from reviewing the latest runtime changes and their affected callers. This verdict covers the safety pass; the later feature and recovery PRs still need their own reviews.

Reviewed on 22 September 2026 at exact head `ad32c4806123f5cfe19d81e81eb7444ef7825750`, in an isolated worktree. [PR #1](https://github.com/Absterrg0/circe/pull/1) passes all 202 repository tests and `pnpm typecheck`. All 30 independent reproductions from the previous rounds pass, together with four new completion and retry checks. The previous reproductions overlap scenarios retained in the repository; they are not 30 additional unique baseline scenarios.

## Why the latest fixes are sufficient

F28 is fixed at both relevant boundaries: concurrent reconciliation calls share one request, and the writer rechecks the monotonic outcome rule after the adapter returns. The additional checks confirm that a transient adapter error leaves reconciliation retryable, and callers receive detached results.

F29 is fixed at each dispatch claim. A pause received while an invocation is awaiting prevents the next pending effect from starting. The added completion check confirms that an explicit resume then starts the retained effect once and finishes the transaction.

F30 is fixed before clarification consumes pending context. An ambiguous answer preserves the pending question and cannot create an executable proposal. A later unambiguous answer completes the same request successfully. This matters because simply disabling the path would have passed the original negative assertion without preserving useful behavior.

## Architecture judgment

These changes enforce the intended safety rules where asynchronous work and interpretation can change execution. The fixes now cover both rejected actions and subsequent valid progress. That supports accepting this safety foundation and moving to the next bounded review.

This round does not establish that the whole product architecture is optimal. F17's durable reconstruction requirement remains an explicit follow-up for PR #3. Real semantic interpretation, persistence behavior and the inspector's end-to-end guarantees must be judged in their respective PRs. Passing the stack's tests alone does not approve those implementations.

## Stack and rebase verification

All six PRs are open, declare the expected successive bases, and have the expected parent chain. PR #2 at `f1634eb` is patch-equivalent to its earlier `9fde66a` version according to `git range-diff`; the recovered accepted-turn changes were retained.

The stack tip `3a7b60e` independently passes typecheck and 261 tests: 229 evals, 14 journal-file, 12 perception-jev and six inspector tests. The 30 prior reviewer reproductions also pass against that tip. This checks regression and rebase integrity, with no full code review of PRs #2–#6 implied.

## Recommendation

Merge PR #1 at the reviewed head, then review PR #2 as the next step. There are no remaining actionable findings within PR #1's agreed scope. The sidecar retains F17 as open and deferred, so the global historical count is one even though this PR's blocker count is zero. No GitHub approval, comment or merge was submitted during this review.

## Reproduce

```sh
cd /tmp/circe-pr1-round4
pnpm test
pnpm typecheck
pnpm exec vitest run --config /tmp/circe-round4-evidence/vitest.config.mjs --reporter=verbose
```

The independent suite returns 34 passed: 18 first-round, nine second-round, three third-round and four completion checks. The downloaded configuration and fixture imports use this machine's isolated-worktree paths; adjust those paths elsewhere. Circe source files were not edited.

## Earlier reports, preserved

# PR #1: three remaining safety gaps

**The previous fixes hold. I would still request changes for three remaining paths.** I verified the exact PR head, `5b165b5cb9d319c4503fe675b6bf811fd6851da1`, in an isolated worktree. This review covers the changes since `5afb275` and the callers affected by those safety checks.

Reviewed on 22 September 2026. [PR #1](https://github.com/Absterrg0/circe/pull/1) now passes all 199 repository tests across 16 files and `pnpm typecheck`. The 18 first-round and nine second-round independent reproductions also pass. Those reruns cover the same scenarios retained in the repository; they are not 27 additional unique baseline scenarios.

## What is fixed

Rejected revisions now revoke pending authorization. The finite-concurrency dispatch loop prevents the reproduced duplicate invocation. Ambiguous affirmations no longer commit. Adapted commands are snapshotted, lost-response batch receipts retain the dispatch key, and late invoke results preserve terminal outcomes. Frame identity and slot exclusion checks pass the earlier reproductions. Completed command IDs now reject conflicting payloads.

The PR also correctly separates the original F17 recovery work from command identity recording. Recovery remains outside this safety-pass verdict. I did not audit the rebased PRs #2-#6 or independently verify their 258-test claim here.

## What remains

The statement that every writer uses the shared checks is still broader than the implementation. `reconcile()` never calls `canAdvanceEffect()`. Dispatch rechecks an effect's PENDING state after an await but does not recheck a pause received in the meantime. The ambiguity guard excludes the clarification branch even though that branch can emit an executable proposal.

These are concrete gaps in the same safety rules under review. The three new tests produce a completed effect becoming UNKNOWN, a new invocation after pause, and execution from an explicitly ambiguous clarification. They do not depend on restart recovery or a real semantic model.

## Next step

Make reconciliation apply the shared outcome guard after its await, recheck dispatch eligibility before every effect claim, and guard clarification consumption before it creates or revises an intent. Then run the 199-test baseline, the 27 previous reproductions and the three new tests against the final PR head. Keep F17 deferred and review the rest of the stack separately.

The reduced set of remaining failures supports keeping the current direction. I would finish these checks before treating PR #1 as the approved foundation for the stack.

## Reproduce

```sh
cd /tmp/circe-pr1-round3
pnpm exec vitest run --config /tmp/circe-round3-evidence/vitest.config.mjs --reporter=verbose
```

On the reviewed commit this yields 27 passed and three failed, all assertion failures. The configuration and test-frame imports use the local isolated-worktree paths; adapt those paths to reproduce elsewhere. No Circe source was edited, and the user's main workspace stayed on its existing branch.

## New findings

### F28: Concurrent reconciliation bypasses the monotonic outcome rule

P1 · `packages/core/src/runtime.ts:1894-1903`

`canAdvanceEffect()` is used by invocation responses, invocation exceptions and observations, but never by `reconcile()`. Reconciliation checks UNKNOWN before calling the executor, then writes the result after an await without reserving the operation or rechecking the effect state.

The reproduction starts two reconciliations of one UNKNOWN effect. The first resolves SUCCEEDED; a delayed result from the second reports UNKNOWN and overwrites that completed outcome. Both the effect and transaction regress. This contradicts the claimed shared rule for every writer, even though the original late-invocation acknowledgement test now passes.

Smallest fix: Deduplicate concurrent reconciliation per effect and apply the same terminal-state guard after the adapter await. Recheck the current effect or reservation before recording any state transition; a late result must not overwrite a terminal outcome.

### F29: An interrupted dispatch loop starts its next pending effect

P1 · `packages/core/src/runtime.ts:695-700`

The revised dispatch loop rechecks whether each effect is still PENDING, but checks `tx.paused` only once at function entry. An interrupt received while `invokeBatch()` is awaiting does not stop the loop from dispatching its next pending effect.

With concurrency set to one, the test starts A and holds its invoke response. It sends an interrupt and verifies that the transaction is paused. Once A returns SUCCEEDED, B invokes without any resume command. The fix prevents duplicate ownership, but the loop still fails to recheck whether dispatch is currently authorized.

Smallest fix: Before claiming every pending effect, recheck transaction pause and lifecycle eligibility as well as effect status and available capacity. Stop the active loop when paused and leave the remaining effects pending for an explicit resume.

### F30: Ambiguous clarification can complete a request into execution

P1 · `packages/core/src/runtime.ts:1606-1616`

Ambiguity checks cover affirm, reject and request, while the fragment/clarify branch still accepts references without checking `classification.ambiguous`. That branch can consume `pendingFrame` and call `applyProposal()`, so it is also a consequence-bearing entry point.

The test first sends an ambiguous unresolved request for task.resume. The runtime asks for a reference. The next classification is an ambiguous clarification naming A at .51 confidence. With a current UI observation and a capability whose commit policy is none, the clarification produces an invocation. No accepted referent decision or affirmative confirmation intervenes.

Smallest fix: Apply semantic acceptance before consuming a clarification into an intent or command. Preserve the pending question on an ambiguous answer, and route accepted clarification-generated proposals and revisions through the same validated consequence boundary.

## Earlier reports, preserved

# PR #1: the previous cases are fixed, but approval still needs changes

**The direction is better and the original repairs are real. I would still request changes.** The PR's shared contracts remove several earlier bypasses. Nine additional required-behavior tests expose gaps that can still execute an invalidated intent, duplicate an effect or accept ambiguous authorization.

Reviewed on 22 September 2026 at commit `5afb2757db227cd3c50e539d61e53f23b12e8ed7`, the exact head of [PR #1](https://github.com/Absterrg0/circe/pull/1). Its base is `68927499f971d9a6302d1cbe2dd98c39a46184d0`. The working project was already on `feat/inspector-e2e`, so I created a detached worktree at `/tmp/circe-pr1-review`. Later recovery, Jev and inspector work is outside this verdict.

## What now passes

- `pnpm test`: 190 passed across 15 files on the PR head.
- `pnpm typecheck`: clean on the PR head.
- The original independent 18 required-behavior tests: all pass, with only local import paths adapted to the isolated worktree.
- The PR retains those scenarios and adds 11 adversarial regressions.

I am not treating the previous failures as still broken. Concurrent duplicate proposals, explicit missing transaction IDs, revision arity, ordinary ambiguous proposals, caller mutation through `command()`, pinned overflow and coverage hashing now pass their reproductions. The new failures combine operations or enter through other paths.

## Why I still would not approve

The shared contracts are called at some boundaries but bypassed at others. `command()` snapshots input, while semantic ingestion directly applies its adapter result. `commit()` checks the transition before awaiting validation but does not fully recheck it afterward. Effect observations obey a terminal-state guard, while executor responses write status directly. A dispatch loop owns a captured list, but resume can start another loop over the same effects.

This is a finite implementation problem with a clear repair direction. Make every writer use the same transitions and immutable inputs, and revalidate ownership after awaits. The existing architecture does not need to be replaced, but adding more branches on top of these gaps will make later debugging harder.

The semantic proposal consumer also accepts independently supplied turn, frame and decision identities without proving they belong together. It checks exclusions against one representation of targets and executes another. Those checks belong in the accepted-turn boundary before any kernel proposal can be emitted.

## What should happen next

1. Close the in-flight invalid-revision race, overlapping dispatch race and late-acknowledgement regression. Test delayed validation, delayed invoke and interleaved resume/observation calls against the same effect.
2. Route accepted semantic commands through the same snapshot and transition handling as typed commands. Apply acceptance to affirmations and other consequence-bearing acts, not only proposals.
3. Bind turns, frames and supporting decisions together, enforce exclusions on executable slots, preserve real batch dispatch keys and check payload identity before returning cached results.
4. Rerun the 190-test suite, the original 18 reproductions and these nine tests on the final PR head. Review the recovery and Jev branches separately after this foundation is corrected.

Restart recovery is explicitly deferred in the PR. That is acceptable for a scoped safety pass, provided the PR stops claiming the full journal finding is closed. The narrow command-ID test passes; reconstruction was part of the broader original finding and remains tracked as F17. It is not included among the nine new failures and is not the reason for requesting changes here.

## Reproduce this review

All failures below are assertion failures in local deterministic tests. No production executor or model was called. No Circe source was edited. The main workspace was left on its existing branch.

```sh
cd /tmp/circe-pr1-review
pnpm exec vitest run --config /tmp/circe-pr1-evidence/vitest.config.mjs --reporter=verbose
```

Expected review output on this commit: 18 previous tests pass; nine additional tests fail. The downloaded config contains local paths. Update its root and aliases and the test-frame imports when reproducing elsewhere.

## New findings

### F19: Rejected revision does not revoke an in-flight confirmation

P1 · `packages/core/src/runtime.ts:1098-1103`

`applyRevision()` marks an invalid revision FAILED but leaves `pendingCommitRevision` and the old intent revision intact. After authority validation completes, `commit()` checks only cancellation and revision identity at lines 802-806, so it proceeds from the FAILED state.

The test starts confirmation, submits an invalid two-target revision to an arity-one slot, verifies the transaction is FAILED, then releases validation. The old action still invokes once. The newer user instruction was rejected, but the old authorization survived it.

Smallest fix: Revoke the pending commit on every intent-invalidating transition. After every await, require both the original reservation identity and a still-committable transaction state before freezing effects.

### F20: Overlapping dispatch loops invoke the same frozen effect twice

P1 · `packages/core/src/runtime.ts:677-679`

`dispatch()` captures a list of PENDING effects once, then awaits each invocation without rechecking an effect's state before starting it. Another resume call can dispatch an effect that remains in the first loop's captured list.

With concurrency set to two, the test holds invocation A, resumes the transaction so B starts, then releases A. The original loop starts B again. Two frozen effects produce three executor calls. The test uses a low-risk capability whose executor declares idempotency unsupported, so a duplicate can repeat the operation.

Smallest fix: Reserve each effect synchronously with a PENDING-to-RUNNING transition immediately before dispatch. Recheck eligibility after every await and share a transaction dispatch coordinator or otherwise prevent overlapping ownership.

### F21: Ambiguous affirmations still authorize execution

P1 · `packages/core/src/runtime.ts:1482-1487`

The new ambiguity guard lives in `proposalFromFrame()`. The affirmation branch ignores that guard and calls `commit()` directly. An ambiguous interpretation of a confirmation therefore still carries execution authority.

The reproduction first creates a transaction requiring affirmative confirmation. Its classifier then returns act=affirm, ambiguous=true and confidence=.51. One invocation occurs. The earlier ambiguous-request regression passes because it exercises a different branch.

Smallest fix: Gate every consequence-bearing semantic act on acceptance before entering act-specific handling. Route accepted confirm, revise, cancel and resume commands through the same validated command mechanism as accepted proposals.

### F22: Classifier-produced commands bypass the immutable input boundary

P1 · `packages/core/src/runtime.ts:1681-1685`

The public `command()` method snapshots and freezes its input, but `ingestReserved()` calls `applyProposal()` or `applyRevision()` directly with the adapter result. The classification object remains owned by the classifier, and nested argument objects are shared through shallow copies.

The test returns a classification with a nested message argument, pauses authority validation, changes that argument through the classifier's retained object, then releases validation. The executor receives the changed message. The snapshot passed into the classifier protects kernel state in one direction; the returned result needs the same treatment.

Smallest fix: Snapshot and validate the adapted command before any application or await. Use a common internal command entry point for typed and semantic inputs so both receive identical isolation and transition checks.

### F23: Late executor acknowledgements overwrite terminal outcomes

P1 · `packages/core/src/runtime.ts:625-634`

`applyEffectObservation()` protects terminal effects only from later observations. `invokeBatch()` assigns status directly after its await, without checking whether an observation already settled the effect.

The test holds an invocation response, ingests a SUCCEEDED effect observation, then releases the earlier response with status RUNNING. The effect regresses to RUNNING and the transaction loses its completed state. A late transport exception can similarly overwrite a known outcome with UNKNOWN.

Smallest fix: Apply one monotonic effect-transition rule to observations, invoke responses, exceptions and reconciliation results. Accept receipt metadata separately from status so a late acknowledgement cannot undo a terminal result.

### F24: Batch reconciliation uses keys that were never dispatched

P1 · `packages/core/src/runtime.ts:642-647`

`invokeBatch()` sends one idempotency key, the first effect ID, for the entire invocation. If the response is lost, its synthesized receipts use each individual effect ID as the idempotency key. All but the first receipt therefore refer to an operation the executor never saw. The no-receipt acknowledgement branch has the same mismatch.

The fake executor records the actual batch key, applies the batch, then throws to simulate a lost response. Reconciliation succeeds for the first effect but returns UNKNOWN for the second. The batch was applied, yet the runtime cannot resolve all of it using its own receipts.

Smallest fix: Record a stable dispatch-operation identity shared by the batch and preserve its actual key in every member receipt. Keep per-effect identity separate. Define whether reconciliation returns a batch result or per-member results and test the lost-response path.

### F25: Semantic turns can borrow evidence from the wrong frame

P1 · `packages/perception/src/proposals.ts:94-96`

`propositionCommand()` now extracts versions from `options.frame`, but never checks that this is the frame identified by `turn.frameId`. An accepted decision set is still just a set of strings; the helper does not establish a connection between those decisions and that frame.

The test builds a turn against frame A@1, creates a later frame A@2, and passes the later frame to the helper. It returns an executable proposal carrying version 2. This recreates the evidence substitution problem even though the old missing-frame test now correctly refuses execution.

Smallest fix: Reject mismatched frame identity before constructing a command. Pass a validated accepted interpretation containing its immutable frame and decision provenance together, and verify the provenance belongs to that frame.

### F26: Exclusions check summary refs but execute slot refs

P1 · `packages/perception/src/proposals.ts:82-91`

Turn exclusion checks compare `proposition.refs`, while command construction prefers `proposition.slots`. These can name different entities, and the helper neither validates their consistency nor applies exclusions to the executable bindings.

The test prohibits B, then supplies an action whose summary refs contain A but whose targets slot contains B. The action explicitly relates to the prohibition through excludes. The check passes and the helper emits a proposal for B with valid version evidence. A contradictory model output is sufficient to bypass the prohibition.

Smallest fix: Normalize executable bindings before checking prohibitions. Either reject contradictions between summary refs and slots or define one canonical source of targets, then enforce all turn-level exclusions against every relevant executable binding.

### F27: Completed command IDs skip payload conflict detection

P2 · `packages/core/src/runtime.ts:449-454`

`runReserved()` returns a completed cached result before comparing content fingerprints. Payload conflict detection therefore works only while the original command is still in flight.

After command same completes for A, the test resubmits same for B. Instead of rejecting the conflicting payload, the runtime returns A's successful result. B does not execute, but the caller receives success for a different request and the documented identity contract is violated.

Smallest fix: Compute and compare the accepted fingerprint before either completed-result or in-flight lookup. Use the same conflict response for both states and retain tests for identical replay and conflicting replay.

## Original report, preserved

# Circe corrective pass review

**Changes requested.** I would not sign off on the claim that the blockers are eliminated. The typed command path is a useful architectural improvement. Several remaining failures can duplicate effects or execute a target different from the one validated.

Reviewed on 22 September 2026. This review covers the uncommitted corrective pass on top of `68927499f971d9a6302d1cbe2dd98c39a46184d0`, including all four new files. The source snapshot manifest identifies the reviewed contents. The [original architecture review](https://circe-semantic-architecture.plan.abstergo.fyi/) remains available unchanged.

## What the tests establish

`pnpm typecheck` passes. `pnpm test` passes all 161 tests across 13 files, including the new 35-test safety suite. I added 18 independent tests asserting required behavior against the new APIs. All 18 fail with assertion failures. These are deterministic, local adapter reproductions; no Jev backend or live executor was involved.

The original 25 defect-assertion tests now fail, but that is insufficient evidence of repair. Several old fixtures no longer supply the required routing adapter, so they never create the transaction under test or fail while accessing an absent result. The new positive tests are stronger evidence. They establish fixes for their covered cases, while the additional tests below disprove the broader guarantees.

## Architectural judgment

The project now has a credible route out of the parser. `SemanticRuntime.command()` accepts a capability directly, and the lexical adapter is outside the kernel. Keep that change. Identity, authoritative observations, bounded context and uncertain semantic decisions remain justified abstractions for this product.

The fragile part is invariant ownership. Proposal validates arity but revision does not. Confirmation protects a pending validation but permits an already running effect to be frozen again. One semantic helper checks acceptance while classifier ingestion can execute ambiguous input. These are signs that the same safety contract is being reimplemented along separate paths.

The next pass should consolidate those contracts. One immutable command boundary should own command identity and transaction selection. One transition function should decide whether the current state permits the action. One intent validator should cover proposal, revision and precommit. The accepted interpretation should carry the evidence that actually supported it all the way to authority validation. Additional flags at individual call sites will leave more bypasses.

## What is genuinely better

- Capability selection can bypass raw tokens through typed commands.
- Null backend output produces structured failure, and score validation checks more of the distribution contract.
- Accepted-value accessors protect ordinary dependent workflow stages.
- Pins survive the seed cap, and provider truncation plus per-candidate omissions are exposed.
- Frames carry more of the stored assertions, observations, relation evidence, event participants and scoped history.
- Observation deduplication and several observation/relation invalidation cases now work.
- Thrown invocation becomes UNKNOWN with a receipt, and in-process reconciliation avoids a blind retry.
- Detached state snapshots and delimiter-safe entity identity address real boundary problems.

## Readiness and work sequence

1. **Close the effect boundary first.** Fix concurrent command deduplication, lifecycle transitions, explicit transaction targeting, immutable inputs, evidence coverage and revision validation. Give effect IDs a real interaction namespace. Require one effect set per intent revision and prove it under delayed validation and delayed invocation.
2. **Make semantic evidence survive consumption.** Use one accepted-turn consumer that validates speech act, operation, prohibitions and dependencies. Reject unsupported structures. Carry exact frame versions and captured knowledge generations. Fix frame size reporting, exact request hashing, invalidation and reused decision IDs before treating model evaluations as reliable evidence.
3. **Then connect Jev in a read-only evaluation path.** A small isolated SDK exploration can start now with effects disabled. Keep the semantic corpus RED until a real interpreter is wired in; do not expand English rules to make it green. Effect-driving integration should wait for the first two steps. Add held-out and metamorphic cases that cover the whole turn, including exclusions and changing evidence.

A disk-backed journal and calibrated risk thresholds can remain deferred for an isolated read-only experiment. Before durability claims, repair the journal's command/transition schema and recovery contract, then choose a persistence adapter and exercise crash boundaries. A persistent implementation cannot reconstruct information the journal never records.

## Remaining end-to-end gap

For "Leave the sketchy auth one alone and continue the other one," the adapter now refuses the mixed clause instead of guessing a capability. That is appropriate containment for bootstrap code. Context assembly can expose relevant evidence, and typed commands can reach the kernel. But there is still no real semantic producer for this utterance, and `propositionCommand()` processes one proposition while ignoring its act, operation and cross-proposition relations. The full accepted-turn-to-safe-effect path is unfinished.

The bounded entity query API is useful, but it does not itself prove scalable retrieval. The in-memory implementation still gathers and sorts entities. Benchmark realistic world sizes before making retrieval performance claims. This review's new tests establish correctness failures, not scale or model calibration.

## Evidence and reproduction

The tests and configuration retain the local paths used during review. Run from `/home/abstergo/Programming/Circe`:

```sh
pnpm exec vitest run --config /tmp/circe-safety-pass-review/vitest.config.mjs --reporter=verbose
```

If reproducing elsewhere, update the config's root and package aliases plus the test-frame import. Desired behavior is encoded as the assertion; failures identify remaining defects. Source snippets below refer to the captured uncommitted files. No Circe source was edited for this review.

## Findings

### F1: Concurrent duplicate proposals still execute twice

P1 · Before effects · `packages/core/src/runtime.ts:1478-1484`

`command()` checks only completed `commandResults`; it does not reserve an in-flight command ID. Two deliveries of the same proposal each create a transaction before either finishes. The per-transaction commit flag cannot deduplicate them.

The required-behavior test submits the same command twice while validation is deferred. Both validations complete and two invokes occur. Sequential duplicate delivery is fixed, but concurrent delivery remains unsafe.

Smallest fix: Reserve a command identity before the first await and share its in-flight result. Check that repeats have identical content; clear or record failures under a defined retry policy.

### F2: Confirming a running transaction creates another effect

P1 · Before effects · `packages/core/src/runtime.ts:480-486`

`commit()` rejects terminal states and an active validation, but allows COMMITTED and DISPATCHED transactions. The pending revision flag is cleared before invocation completes, and existing frozen effects are not checked.

After an executor returns RUNNING, a typed confirmation with a new command ID for the same transaction and revision validates, freezes and invokes again. The reproduction records two invokes for one intent revision.

Smallest fix: Restrict confirmation to explicitly committable states and atomically associate one frozen effect set with an intent revision. Repeated confirmation of that revision must never create another set.

### F3: An unknown transaction ID targets unrelated live work

P1 · Before effects · `packages/core/src/runtime.ts:1491-1498`

`transactionById(command.transactionId) ?? gateTransaction() ?? liveTransaction()` treats an explicit but missing ID as if no ID was supplied. Revision, cancellation and resumption have similar fallback behavior.

The reproduction creates one pending transaction, then confirms `tx:missing`. The existing transaction executes. A stale or mistyped reference therefore authorizes different work, contrary to the no-substitution rule.

Smallest fix: Use fallback selection only when the command omits transactionId. Reject any explicit ID that is missing, and apply the same rule consistently to confirm, revise, cancel and resume.

### F4: Revision can resurrect a cancelled transaction

P1 · Before effects · `packages/core/src/runtime.ts:861-888`

`applyRevision()` rejects transactions with effects but never rejects a terminal transaction addressed by ID. Cancellation before dispatch leaves no effects, so the revision is accepted and replaces CANCELLED with a new gate state.

The test cancels a transaction, revises its unchanged revision by explicit ID, then confirms revision 2. The cancelled action executes. This violates the transaction lifecycle even without concurrency.

Smallest fix: Validate the transition state before applying a revision. Terminal transactions stay terminal; further work must create an explicit new intent.

### F5: Mutable command objects can change the target after validation starts

P1 · Before effects · `packages/core/src/runtime.ts:289-309`

`makeSlotBinding()` copies arrays and precondition wrappers but shares nested refs and tokens with the caller. Arguments and other proposal fields also receive only shallow copies.

The test starts validation of A, changes `command.slots[0].refs[0].id` to B, then releases validation. The adapter recorded A as the checked target, but the executor receives B. Returning a detached `state()` snapshot does not protect this input boundary.

Smallest fix: Snapshot and validate the entire incoming command before storing it or awaiting anything. Freeze immutable intent/effect inputs and avoid sharing mutable nested objects with caller or adapter code.

### F6: Effect identities still collide across runtime instances

P1 · Before shared executors · `packages/core/src/runtime.ts:185-191`

`nextEffectId()` includes `state.interactionId`, but every runtime initializes that value to the literal `interaction:1`. There is no RuntimeDeps option to assign a distinct interaction identity.

Two independently created runtimes both send `effect:interaction:1:1` as their first idempotency key. A shared idempotent executor can suppress legitimate work or return another operation's result. Restart also resets the counters.

Smallest fix: Require an injected, stable interaction identity and persist operation identity allocation. Test two concurrent interactions against the same executor and recovery against an existing journal.

### F7: Explicitly incomplete evidence silently falls back to newer snapshots

P1 · Before effects · `packages/core/src/runtime.ts:801-820`

`applyProposal()` filters supplied expectedVersions for each slot, then passes an empty array to `makeSlotBinding()`. That helper treats an empty array as absent and fills in versions from current kernel views. This contradicts the comment saying incomplete declared evidence will be rejected.

The test supplies evidence for an unrelated ref while A exists in a version-2 view. Instead of rejecting missing evidence for A, the kernel validates A at version 2 and invokes.

Smallest fix: Distinguish absent evidence from explicitly supplied empty or non-covering evidence. Never fill holes in a supplied read set with current snapshots; reject incomplete coverage.

### F8: Semantic proposals lose the versions that supported the decision

P1 · Before connecting perception to effects · `packages/perception/src/proposals.ts:19-34`

`propositionCommand()` accepts a frame ID and decision ID set but receives no frame/read set and emits no expectedVersions. SemanticProposition has no observation-version field. Neither acceptance nor dependency freshness is checked against the store here.

The test interprets A from a version-1 ContextFrame, updates the kernel view to version 2, then builds a command with this helper. The kernel silently binds and validates version 2. It never tests the version that supported the model's judgment.

Smallest fix: Build proposals from an accepted interpretation plus its immutable frame/read set. Carry exact observation versions and captured knowledge generations; reject stale support rather than reinterpreting it as current.

### F9: The proposition consumer turns queries into execution proposals

P1 · Before connecting perception to effects · `packages/perception/src/proposals.ts:14-25`

The new consumer only checks turn status, a capability, accepted decision IDs and include polarity. It ignores proposition.act, operation and relations, and always returns kind `propose`. Empty decision lists also pass its `every()` check.

A query/inspect proposition naming `task.resume` returns an executable proposal in the required-behavior test. The helper cannot distinguish asking about an operation from requesting it. Exclusion/dependency relationships between propositions are likewise not compiled.

Smallest fix: Validate allowed act/operation combinations and compile the turn as a whole, preserving prohibitions and dependencies. Emit no executable command for unsupported or incomplete semantic structures.

### F10: The classifier-frame path bypasses the ambiguity guard

P1 · Before semantic ingest · `packages/core/src/runtime.ts:1053-1089`

`proposalFromFrame()` returns a proposal even when `classification.ambiguous` is true. It adds pendingCapability as extra metadata, but request handling executes the proposal first. This path bypasses the accepted-decision guard in the separate proposition helper.

The test supplies an ambiguous classification with capability, slots and complete version evidence. `ingest()` invokes the executor. This reproduces an actual effect path, not merely an ambiguous answer being exposed to a later stage.

Smallest fix: Reject ambiguous classifications before building or applying executable commands. Prefer one validated semantic-command path so frame-based ingestion cannot bypass the acceptance rules.

### F11: Oversized pinned frames are reported as within budget

P1 · Before model evaluation · `packages/context/src/assembly.ts:558-590`

`frameSizeExceeded` becomes true only after a soft candidate is removed. If all remaining candidates are pinned, `dropIndex < 0` breaks the loop while leaving the flag false. The size calculation also counts JavaScript code units rather than UTF-8 bytes.

One explicitly pinned candidate with a 300,000-character attribute exceeds the default 262,144 budget, remains fully included, and reports frameSizeExceeded=false. This can send an oversized request without the promised explicit failure signal.

Smallest fix: Calculate whether the final complete serialized request exceeds the budget independently of whether anything was dropped. Preserve pins but return an explicit unusable/needs-expansion outcome. Measure the documented byte unit.

### F12: The exact-input hash omits coverage metadata

P1 · Before model evaluation · `packages/context/src/assembly.ts:535-553`

The hash payload is built before completeness and excludes it. It also differs from the full ContextFrame passed to the backend. The capability-order case is fixed, but exact-input provenance is not.

Two providers return identical seeds and different truncated flags. The resulting frames have different completeness and can trigger different acceptance policy, yet their hashes are equal. With the same store version, their frame IDs also coincide; recordFrame treats the second as the already stored first frame because only hashes are compared.

Smallest fix: Define and persist one canonical model request, including coverage and relevant policy metadata, then hash that representation. Keep semantic cache keys separate from exact request identity.

### F13: Late neighborhood judgments capture the new generation at publication

P1 · Before reusable semantic beliefs · `packages/context/src/store.ts:278-290`

Annotations implicitly depend on each subject's current generation at insertion time, not the generation read during inference. Only explicit dependsOn values are checked for staleness. The helper does not derive the read generations from the recorded context frame.

The test assembles a frame, adds a new blockedBy relation while its interpretation is outstanding, then inserts an annotation depending on the unchanged observation. It remains active because the new generation is attached after the change. Capturing the old generation explicitly can avoid this, but the default claimed guarantee does not.

Smallest fix: Capture generations in the interpretation's read set and require them when publishing a belief. Validate those captured values atomically; do not infer freshness from the publication-time generation.

### F14: New obligation events do not invalidate prior judgments

P1 · Before reusable semantic beliefs · `packages/context/src/store.ts:255-263`

Knowledge generations advance on observation and relation changes, but `recordEvent()` only appends and bumps the store version. Existing beliefs depending on a subject generation remain active.

The test creates a belief from current knowledge and then records a new obligation-opened event for the same entity. The belief remains valid. This directly affects questions such as 'which one needs me?' where obligations are material evidence.

Smallest fix: Declare and advance event/obligation read generations, or a broader subject generation where appropriate. Test event additions, resolutions, removals and temporal expiry against dependent beliefs.

### F15: A rejected retry can leave the old answer in accepted state

P1 · Before model evaluation · `packages/perception/src/orchestrator.ts:331-335`

Spec IDs are checked for duplicates only inside each stage. If a later stage reuses an ID, raw answers and outcomes are replaced, but accepted is updated only for auto-bind and never cleared when the new answer is rejected.

The test asks spec x twice. The first result is accepted at .99; the second requires clarification at .55. `choiceOf(state, 'x')` still returns the first accepted answer. The aggregate run stays AMBIGUOUS, but accepted-value access and further dependencies contradict the latest outcome.

Smallest fix: Either reject reused spec IDs across the workflow or define explicit attempts and clear/recompute accepted values and dependent decisions when an attempt changes the outcome.

### F16: Revision skips slot arity validation

P1 · Before effects · `packages/core/src/runtime.ts:890-908`

`applyProposal()` checks arity-one slots, but `applyRevision()` checks only that slot names exist. `missingRequired()` and commit do not enforce arity later.

The test creates a one-target intent, revises the targets slot to contain A and B, then confirms. Both targets reach execution despite the descriptor allowing one.

Smallest fix: Use a shared intent/binding validator on proposal, revision and precommit. Validate arity, duplicate slot names, required values and supported argument schemas consistently.

### F17: The journal omits typed commands and their deduplication identities

P2 · Before durability claims · `packages/core/src/runtime.ts:1478-1488`

`command()` does not append the command. InteractionLogEntry has no typed-command record, and intent-revised stores only an ID and revision. The runtime neither reads an existing journal nor reconstructs commandResults. A persistent append implementation alone cannot add those missing semantics.

After a typed proposal with commandId unique-command-123 completes, that command ID appears nowhere in the journal. The test fails even before attempting restart. Deferring a disk-backed implementation is reasonable; this is a separate missing recovery contract.

Smallest fix: Record the full accepted command and identity, reconstructible intent/transaction transitions, and command results or sufficient replay events. Add a recovery entry point and crash-boundary tests before claiming durability.

### F18: Clear-selection without refs leaves the selection active

P2 · Before UI use · `packages/core/src/runtime.ts:1004-1041`

`clear-selection` handling is nested inside `if (event.refs && event.refs.length > 0)`. A clear event naturally needs no refs, so it bypasses that branch.

The test selects A and then sends `{ action: 'clear-selection' }`. currentReferentId still points at A. Subsequent deictic interpretation can continue using a selection the user cleared.

Smallest fix: Handle clear-selection independently of supplied refs, and explicitly define which focus, scope and referent records should be cleared.
