PR #2: two blockers remain

Changes requested

234 baseline tests pass · 41 prior reproductions pass · 4 new cases fail

2 P1 findings · genuine shared-dependency control passes

Incremental +411 / -36 34685ba

The original F31-F37 reproductions pass at 34685ba, but I would still request changes. One dependency check remains specific to the whole-turn entry point. The new read-dependency field also duplicates slot evidence, producing inconsistent versions after revisions and false dependencies between target effects.

Reviewed on 22 September 2026 at exact PR #2 head 34685ba2cbb50d7d029437a040d1d3a6dfe0eac6. This incremental review covers changes since 91c123b and their affected callers. PR #2 was unchanged at verification. Circe source files were not edited.

What now works

Both entry points use the new whole-turn shape validation and executable act/operation checks. The explicit index must match its proposition, dangling relations fail closed, and frame id and hash are both checked. The whole-turn compiler rejects a dependent when its earlier prerequisite was rejected. Argument-only reference evidence now reaches authority validation and frozen effects.

All 41 independent prior reproductions pass: 18 first-round safety cases, nine second-round cases, three third-round cases and the 11 PR #2 cases. Those scenarios overlap the tests now retained in the repository. PR #2 independently passes 234 eval tests and typecheck.

What still breaks

The per-proposition API never consults the whole-turn rejected-prerequisite analysis. A dependent rejected by compileSemanticTurn is still returned as a command by propositionCommand. The new shared helper covers act and shape checks, not the complete acceptance contract.

The evidence fix copies all expected versions into shared dependencies. Slot expectations already own many of those versions. After a revision, the two copies disagree: validation checks version 2 but dispatch sends version 1. Retargeting from A to B can retain an obsolete A precondition. Splitting an A/B command into sequential target effects makes B depend on A's pre-mutation version, so successful execution of A invalidates B.

These evidence cases pass at the preceding commit and fail at the reviewed commit. They are regressions, not added requirements for a future feature. A passing control checks that genuine argument dependencies still reach every effect, so dropping shared evidence altogether would not be a valid repair.

Recommendation

Keep this architecture, but finish the shared analysis and define evidence ownership. Both APIs should select commands from the same validated turn. Slot-owned expectations and genuine shared read dependencies need distinct lifetimes and effect scope. Freeze exactly the versions that were validated for the current intent revision.

There are two P1 findings, F38 and F39, with four failing required-behavior cases. The original seven findings retain their bodies and are marked fixed for their original scenarios. Related remaining behavior is recorded separately so the review history stays accurate.

Stack verification

The declared bases for PRs #2-#6 match the reported stack. Tip 623e2d7 independently passes typecheck and 281 tests: 249 evals, 14 journal-file, 12 perception-jev and six inspector tests. All 41 prior probes and the shared-dependency control pass there too. All four new failures also reproduce on that tip. This is a regression check, not a full code review of the later PRs.

Reproduce

cd /tmp/circe-pr2-round2
pnpm exec vitest run --config /tmp/circe-pr2-round2-evidence/vitest.config.mjs --reporter=verbose

Expected result at the reviewed head: 42 passed and four failed. The 42 comprise the prior 41 cases and one additional control. The external probe file also passes strict typecheck. Downloaded configs contain local checkout paths; adjust them on another machine. No formal GitHub approval, comment or merge was submitted.

New probes and control Test config 41 prior cases Current output Evidence cases before fix Stack probe output PR #2 baseline PR #2 typecheck Stack baseline Stack typecheck Review sidecar

Changed files this round
Current findings
Prompt for current findings · Ctrl+`
Prompt to rerun review · Ctrl+Space
P1 F39. Slot evidence is incorrectly retained as a shared read dependencypackages/core/src/runtime.ts:1088-1096logic data-integrity

applyProposal copies every command.expectedVersions entry into intent.readDependencies, including refs already owned by slot bindings. Revisions update the bindings but preserve this second copy through the intent spread. commit deduplicates with binding versions winning, while effect construction at lines 896-899 reverses that precedence and lets readDependencies win.

The first reproduction proposes A at version 1, revises its binding to version 2 and confirms. Authority validation receives version 2; the executor receives version 1. A second reproduction retargets the intent from A to B. A is no longer a target, but its retained version still reaches validation and blocks B when A has changed.

The same copy also breaks independent target effects. Every effect receives the entire readDependencies list, so an A/B request with maxConcurrentEffects:1 gives the B invocation a precondition on A. An executor that atomically checks preconditions updates A first, then refuses B because A is now newer. Only A is applied. All three evidence tests pass at 91c123b and fail at 34685ba; these are regressions introduced by the evidence repair.

The shared argument-evidence fix itself works: a separate passing control verifies that a genuine argument ref C survives into both effects. The repair must retain that behavior while distinguishing slot-owned evidence from true shared dependencies.

Fix direction. Represent slot-owned expectations and shared read dependencies separately at the command boundary. Derive a canonical evidence set for each intent revision, validate and freeze the same versions, retire evidence owned only by replaced bindings, and attach only genuine shared dependencies to every target effect. Preserve argument dependencies; do not remove all shared evidence as a workaround.

Source at reviewed head · Probe N2, N3, N4 · Related to F32

Captured source
1088:     // The declared read set is command-wide: refs carried in arguments (or
1089:     // otherwise not bound to a slot) must still be validated and frozen.
1090:     const readDependencies = dedupePreconditions(command.expectedVersions ?? []);
1091:     const intent: Intent = {
1092:       id: `intent:${++state.counters.intent}`,
1093:       revision: 1,
1094:       capability: command.capability,
1095:       bindings,
1096:       ...(readDependencies.length > 0 ? { readDependencies } : {}),
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/core/src/runtime.ts` around lines 1088-1096, applyProposal copies every command.expectedVersions entry into intent.readDependencies, including refs already owned by slot bindings. Revisions update the bindings but preserve this second copy through the intent spread. commit deduplicates with binding versions winning, while effect construction at lines 896-899 reverses that precedence and lets readDependencies win. Represent slot-owned expectations and shared read dependencies separately at the command boundary. Derive a canonical evidence set for each intent revision, validate and freeze the same versions, retire evidence owned only by replaced bindings, and attach only genuine shared dependencies to every target effect. Preserve argument dependencies; do not remove all shared evidence as a workaround. Reproduce N2, N3, N4 using `/tmp/circe-pr2-round2-evidence/round2.test.ts` at PR #2 head 34685ba2cbb50d7d029437a040d1d3a6dfe0eac6. Retain the 41 prior reviewer cases and the genuine argument-dependency control, and run the full suite and typecheck.
P1 F38. The per-proposition API still bypasses prerequisite rejectionpackages/perception/src/proposals.ts:521-527logic correctness

The new whole-turn compiler tracks rejectedIndexes and prevents a dependent proposition from compiling when its prerequisite is rejected. propositionCommand still checks only whether the prerequisite decision ids are accepted. Shared shape and act checks do not make these dependency rules shared.

The reproduction uses an accepted request for B without a frame observation, followed by an accepted request for A with dependsOn:0. compileSemanticTurn correctly emits no commands. propositionCommand on the dependent request, with the correct explicit index, returns an executable command for A. This is the remaining entry-point path of F33; its original whole-turn reproduction now passes.

Fix direction. Share the complete dependency-validity analysis between both entry points, including evidence and prohibition rejection, or require the per-proposition helper to consume an already validated turn. Sharing only executableIssue is insufficient.

Source at reviewed head · Probe N1 · Related to F33

Captured source
521:   const isAccepted = (candidate: SemanticProposition): boolean =>
522:     decisionsAccepted(candidate, options.acceptedDecisions);
523:   const analysis = analyzeTurn(turn, isAccepted, issues);
524:   if (analysis.prohibited.has(index) || analysis.unvalidatedProhibited.has(index)) {
525:     return undefined;
526:   }
527:   if (relationIssue(turn, proposition, index, isAccepted)) return undefined;
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 521-527, The new whole-turn compiler tracks rejectedIndexes and prevents a dependent proposition from compiling when its prerequisite is rejected. propositionCommand still checks only whether the prerequisite decision ids are accepted. Shared shape and act checks do not make these dependency rules shared. Share the complete dependency-validity analysis between both entry points, including evidence and prohibition rejection, or require the per-proposition helper to consume an already validated turn. Sharing only executableIssue is insufficient. Reproduce N1 using `/tmp/circe-pr2-round2-evidence/round2.test.ts` at PR #2 head 34685ba2cbb50d7d029437a040d1d3a6dfe0eac6. Retain the 41 prior reviewer cases and the genuine argument-dependency control, and run the full suite and typecheck.
Original review, baseline and finding bodies preserved

The following report describes 91c123b. All seven original finding scenarios now pass. Open archive.

PR #2 needs changes before merge

Changes requested

6 P1 findings · 1 P2 finding · 223 baseline tests pass

10 required-behavior cases fail · valid-request control passes

+1531 / -84 91c123b

The accepted-turn bridge is the right architectural direction, but I would request changes at 91c123b. Six blocking findings and one smaller identity inconsistency remain. They are reproduced below, with ten failing required-behavior cases and one passing valid-request control.

Reviewed on 22 September 2026 against merged base 3d3bf9b, through exact head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. This is a review of PR #2, including its latest corrective commit. The main workspace remains unchanged. Tests ran in an isolated worktree.

Assessment

I would keep the frame-to-turn-to-command architecture. Recording accepted judgments, checking capability citations and applying exclusions before execution are useful responsibilities for this layer. There is no need to add English rules or replace the transaction kernel to resolve these findings.

The implementation has two partially independent validators. One rejects queries but ignores malformed sibling exclusions. The other rejects malformed siblings but now executes queries. This is the immediate architectural weakness: a caller's choice of API changes the safety contract. The latest refactor removed a call to the older helper and accidentally removed its act/operation checks from the new path.

The second boundary problem crosses packages. The compiler claims to cover refs in arguments, but the kernel drops their evidence unless those refs also occupy slots. Adding more compiler checks cannot compensate for a consumer that discards the certified read set.

Use one validated whole-turn representation and a narrow command constructor. Either preserve command-wide read dependencies through the kernel or refuse unsupported argument refs. Rejected prerequisites must not leave independently executable dependents behind. These are bounded repairs; a larger framework would not address the demonstrated failures.

Verification and test quality

pnpm test passes all 223 tests across 19 eval files, and pnpm typecheck passes. The PR description's 202-test figure is stale. The independent probe suite has ten assertion failures covering seven findings; its valid-request control passes. The probe file also passes the repository's strict TypeScript settings.

The query test already in the repository cites a referent decision, so capability-citation rejection makes it pass without testing the act guard. The independent query cases supply valid capability evidence, and each reaches an actual executor invocation. Evidence coverage is tested across compiler and kernel, including an authority that would reject a stale argument ref if that ref reached validation.

The difficult-utterance E2E test exercises a useful contract: exclusion of B, action on A and preservation of frame versions. Its backend answers and propositions are scripted. It is not evidence that the implementation can interpret arbitrary wording, and that remains a legitimate boundary for this PR.

Next step

Fix F31-F36 before merging. F37 is a smaller provenance inconsistency worth fixing in the same shared validation pass. Keep all 223 existing tests passing, retain these required-behavior probes, and rerun them against the final PR head. No GitHub comment, formal approval or source change was submitted by this review.

cd /tmp/circe-pr2-review
pnpm test
pnpm typecheck
pnpm exec vitest run --config /tmp/circe-pr2-evidence/vitest.config.mjs --reporter=verbose

The downloaded test and config use local worktree paths. Adjust aliases for another checkout. Finding numbers continue from the earlier safety review; PR #1's scoped approval is unchanged.

Implementation walkthrough
  • semanticTurnFromRun maps accepted spec ids to trace ids and carries run/frame identity into the turn.
  • compileSemanticTurn checks citation provenance and capability selection, computes exclusions, derives observed versions and suppresses identical commands.
  • The latest refactor constructs commands directly, leaving act/operation checks only in propositionCommand.
  • The compiler now recognizes argument refs, but the kernel still stores read evidence only on slot bindings.
  • The new evals demonstrate contract behavior with scripted decisions and manually constructed propositions; real interpretation remains a later integration concern.
Workflow run → accepted decision ids + traces → whole-turn validation → evidence-bound commands → kernel validation/freeze → executor
Changed files
  • packages/context/src/types.ts +2 / -0
  • packages/evals/src/accepted-turn-adversarial.test.ts +350 / -0
  • packages/evals/src/accepted-turn-pipeline.test.ts +542 / -0
  • packages/perception/src/proposals.ts +631 / -84
  • packages/perception/src/trace.ts +1 / -0
  • packages/perception/src/types.ts +5 / -0

Findings

Required-behavior tests Vitest config Probe output Baseline output Typecheck output Review sidecar

Prompt for all findings · Ctrl+`
Prompt to rerun review · Ctrl+Space
P1 F34. A dangling prohibition is silently ignoredpackages/perception/src/proposals.ts:123-135logic correctness

shapeIssue checks that a relation targetIndex is an integer, but does not check its bounds. Bounds are checked later in relationIssue, after exclusions have already returned from the compilation loop. The whole-turn malformed-input rule consequently misses dangling relations, including prohibition-only propositions.

The reproduction submits an exclusion with no direct refs and an excludes relation targeting index 99 in a two-proposition turn. The exclusion is labeled an exclusion constraint, while the other proposition compiles into a command. The malformed prohibition was never validated or rejected as malformed.

The PR expressly promises that malformed relations make the whole turn uncompilable because ignoring them can discard intended prohibitions. The current test covers a string index, but an out-of-range integer passes that shape gate.

Fix direction. Validate relation bounds and target existence for the entire turn before analyzing exclusions or constructing any commands. Apply the same validation to exclusion-only propositions and both public entry points.

Source at reviewed head · Probe R4

Captured source
123:   if (proposition.relations !== undefined) {
124:     if (!Array.isArray(proposition.relations)) {
125:       return "malformed proposition: relations is not an array";
126:     }
127:     for (const relation of proposition.relations) {
128:       if (
129:         !isRecord(relation) ||
130:         typeof relation.kind !== "string" ||
131:         !RELATION_KINDS.has(relation.kind) ||
132:         !Number.isInteger(relation.targetIndex)
133:       ) {
134:         return "malformed proposition: relation is not a typed, integer-indexed reference";
135:       }
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 123-135, shapeIssue checks that a relation targetIndex is an integer, but does not check its bounds. Bounds are checked later in relationIssue, after exclusions have already returned from the compilation loop. The whole-turn malformed-input rule consequently misses dangling relations, including prohibition-only propositions. Validate relation bounds and target existence for the entire turn before analyzing exclusions or constructing any commands. Apply the same validation to exclusion-only propositions and both public entry points. Reproduce R4 using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.
P1 F33. Rejected prerequisites do not block dependent commandspackages/perception/src/proposals.ts:355-359logic correctness

relationIssue treats a dependsOn target as satisfied when its cited decisions are accepted. It never checks whether that proposition survives evidence validation, prohibitions or other compilation checks. compileSemanticTurn then returns the dependent command with no representation of the relation.

The reproduction has proposition 0 request B without a frame observation, and proposition 1 request A with dependsOn:0. The compiler explicitly rejects proposition 0 for missing evidence yet emits proposition 1. A caller executing the returned commands cannot discover the missing prerequisite from that command.

Acceptance of a judgment is weaker than acceptance of the proposition it supports. A whole-turn compiler needs to preserve that distinction. This finding concerns a rejected prerequisite at compilation; it does not require adding a general workflow scheduler to this PR.

Fix direction. Compute proposition validity before emitting commands and propagate rejection through dependency edges. Reject executable dependency forms that the command contract cannot preserve; define semantic-only dependencies explicitly rather than silently treating rejected requests as satisfied.

Source at reviewed head · Probe R3

Captured source
355:     const related = turn.propositions[relation.targetIndex];
356:     if (!related) return "malformed proposition: relation target is missing";
357:     if (relation.kind === "dependsOn") {
358:       if (!isAccepted(related)) return "a dependency proposition was not accepted";
359:     }
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 355-359, relationIssue treats a dependsOn target as satisfied when its cited decisions are accepted. It never checks whether that proposition survives evidence validation, prohibitions or other compilation checks. compileSemanticTurn then returns the dependent command with no representation of the relation. Compute proposition validity before emitting commands and propagate rejection through dependency edges. Reject executable dependency forms that the command contract cannot preserve; define semantic-only dependencies explicitly rather than silently treating rejected requests as satisfied. Reproduce R3 using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.
P1 F35. The explicit proposition index can bypass prohibitionspackages/perception/src/proposals.ts:453-469logic correctness

The new index option is trusted without checking its range or confirming that turn.propositions[index] matches the supplied proposition. The code uses that index to look up turn-wide prohibitions, while it constructs the command from the separate proposition argument.

The reproduction places a relation-only exclusion at index 0 prohibiting the request at index 1. Calling propositionCommand with that request and index:0 returns an executable command. Checking the correct position would reject it. Rehydration or filtering code can therefore make the guard inspect one proposition while executing another.

This is a defect in the new exported low-level API even though compileSemanticTurn no longer calls it. The API explicitly exists for callers holding accepted decisions and advertises the same turn-wide prohibitions.

Fix direction. Require an integer index within bounds and verify identity or canonical structural equality with the selected turn entry. Reject mismatches before reading prohibitions, or accept only the turn and index and resolve the proposition internally.

Source at reviewed head · Probe R5

Captured source
453:   // Prefer an explicit position; fall back to identity, then to a unique
454:   // structural match so rehydrated turns are not silently dropped.
455:   let index = options.index ?? turn.propositions.indexOf(proposition);
456:   if (index < 0) {
457:     const serialized = stableStringify(proposition);
458:     const matches = turn.propositions
459:       .map((candidate, candidateIndex) => ({ candidate, candidateIndex }))
460:       .filter(({ candidate }) => stableStringify(candidate) === serialized);
461:     if (matches.length === 1) index = matches[0]!.candidateIndex;
462:   }
463:   if (index < 0) return undefined;
464:
465:   const issues = turn.propositions.map((candidate) => shapeIssue(candidate));
466:   const isAccepted = (candidate: SemanticProposition): boolean =>
467:     decisionsAccepted(candidate, options.acceptedDecisions);
468:   const analysis = analyzeTurn(turn, isAccepted, issues);
469:   if (analysis.prohibited.has(index) || analysis.unvalidatedProhibited.has(index)) {
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 453-469, The new index option is trusted without checking its range or confirming that turn.propositions[index] matches the supplied proposition. The code uses that index to look up turn-wide prohibitions, while it constructs the command from the separate proposition argument. Require an integer index within bounds and verify identity or canonical structural equality with the selected turn entry. Reject mismatches before reading prohibitions, or accept only the turn and index and resolve the proposition internally. Reproduce R5 using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.
P1 F36. The per-proposition API drops malformed exclusionspackages/perception/src/proposals.ts:465-468logic correctness

propositionCommand computes shape issues for every proposition but never rejects a malformed turn. analyzeTurn skips any proposition with a shape issue. In contrast, compileSemanticTurn rejects the whole turn before this step.

The reproduction supplies an exclusion of A whose relations field is malformed, alongside a valid request for A. The whole-turn API correctly rejects it. The per-proposition API skips the malformed exclusion and emits the request for A, complete with current version evidence.

The unvalidated-exclusion sets do not help here because the malformed proposition is skipped before any refs enter those sets. An API promising fail-closed exclusions must not turn a malformed constraint into permission.

Fix direction. Apply one whole-turn structural validation pass to both entry points. A malformed sibling must prevent command construction, or make the low-level helper internal to an already validated turn.

Source at reviewed head · Probe R6

Captured source
465:   const issues = turn.propositions.map((candidate) => shapeIssue(candidate));
466:   const isAccepted = (candidate: SemanticProposition): boolean =>
467:     decisionsAccepted(candidate, options.acceptedDecisions);
468:   const analysis = analyzeTurn(turn, isAccepted, issues);
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 465-468, propositionCommand computes shape issues for every proposition but never rejects a malformed turn. analyzeTurn skips any proposition with a shape issue. In contrast, compileSemanticTurn rejects the whole turn before this step. Apply one whole-turn structural validation pass to both entry points. A malformed sibling must prevent command construction, or make the low-level helper internal to an already validated turn. Reproduce R6 using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.
P1 F31. The whole-turn compiler turns queries into mutationspackages/perception/src/proposals.ts:605-616logic correctness

compileSemanticTurn no longer calls propositionCommand. It calls buildCommand directly after citation and relation checks, but never checks that the act is request or that the operation is reference or slot-fill. Those checks exist only in the other API at lines 445-449. The latest 91c123b refactor removed their effective use by the whole-turn compiler.

A resolved query/inspect proposition with an accepted task.resume capability citation and an observed target compiles to a propose command and reaches the executor. The same happens for query/reference and request/compose. All three required-behavior tests fail on an actual invocation, not merely on command construction.

The existing query regression supplies a referent citation rather than the capability citation needed to get this far, so it passes for a different rejection reason. The kernel receives no dialogue act and cannot repair the lost distinction.

Fix direction. Use one executable-proposition validator for both entry points, including capability presence and act/operation eligibility. Test non-executable acts with otherwise valid citations and evidence.

Source at reviewed head · Probe R1

Captured source
605:     const issue = citationIssue(proposition, turn, acceptedDecisions, tracesById, tracesAvailable);
606:     if (issue) {
607:       rejected.push({ index, reason: issue });
608:       return;
609:     }
610:     const relation = relationIssue(turn, proposition, index, accepted);
611:     if (relation) {
612:       rejected.push({ index, reason: relation });
613:       return;
614:     }
615:
616:     const declared = slotsOf(proposition);
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 605-616, compileSemanticTurn no longer calls propositionCommand. It calls buildCommand directly after citation and relation checks, but never checks that the act is request or that the operation is reference or slot-fill. Those checks exist only in the other API at lines 445-449. The latest 91c123b refactor removed their effective use by the whole-turn compiler. Use one executable-proposition validator for both entry points, including capability presence and act/operation eligibility. Test non-executable acts with otherwise valid citations and evidence. Reproduce R1 using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.
P1 F32. Argument reference evidence disappears at the kernel boundarypackages/perception/src/proposals.ts:665-676logic data-integrity

The new argument-reference path adds versions for argument-only refs to command.expectedVersions. However, the kernel in runtime.ts:1051-1057 filters that list to refs present in named slot bindings. Its commit path validates and freezes only binding expectations. The new compiler therefore emits evidence that its actual consumer silently drops.

The reproduction compiles slot target A plus arguments.secondary = B. The command correctly includes versions for A and B, but authority validation receives only A and the executor receives the B argument without a B precondition. A second test makes authority validation return stale for B if asked. Execution still happens because B is never checked.

This breaks the newly claimed coverage of every executable argument reference. Seeing B in the frame at compile time does not establish that B is current at dispatch. No real backend or persistence adapter is required to reproduce the gap.

Fix direction. Until command-wide read preconditions survive validation and freezing, reject argument refs outside executable bindings. Alternatively add explicit command-level read dependencies and preserve them through every effect unit; do not silently invent target slots that change execution semantics.

Source at reviewed head · Probe R2 / R2b

Captured source
665:     // Every executable ref must carry the version the interpretation read.
666:     const evidence = expectedVersionsFromFrame(options.frame, [
667:       ...constrained.flatMap((slot) => slot.refs),
668:       ...argumentRefs,
669:     ]);
670:     for (const ref of [...constrained.flatMap((slot) => slot.refs), ...argumentRefs]) {
671:       if (!evidence.some((precondition) => entityRefKey(precondition.ref) === entityRefKey(ref))) {
672:         rejected.push({ index, reason: `no frame observation for ${entityRefKey(ref)}` });
673:         return;
674:       }
675:     }
676:     const command = buildCommand({ proposition, commandId, slots: constrained, argumentRefs, evidence });
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 665-676, The new argument-reference path adds versions for argument-only refs to command.expectedVersions. However, the kernel in runtime.ts:1051-1057 filters that list to refs present in named slot bindings. Its commit path validates and freezes only binding expectations. The new compiler therefore emits evidence that its actual consumer silently drops. Until command-wide read preconditions survive validation and freezing, reject argument refs outside executable bindings. Alternatively add explicit command-level read dependencies and preserve them through every effect unit; do not silently invent target slots that change execution semantics. Reproduce R2 / R2b using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.
P2 F37. Frame binding checks content but omits identitypackages/perception/src/proposals.ts:533-550logic data-integrity

The whole-turn compiler checks frame.hash against turn.frameHash but does not compare frame.id with turn.frameId. propositionCommand checks both. Before the latest refactor the whole-turn compiler reached that identity check through the helper; it no longer does.

The reproduction supplies the same content and hash under a different frame id. The turn still compiles. This is a provenance and contract inconsistency; the probe does not demonstrate different world content or an unsafe mutation, so it is lower severity than the execution defects.

The accepted-turn contract and PR description record and promise both frame id and hash. Enforcing one of them in only one API makes the evidence-binding behavior depend on which compiler the caller chooses.

Fix direction. Require both frame id and hash to match in the shared validation path. If content equivalence is intentionally sufficient, make that explicit across the contract and both APIs instead of silently changing it in one.

Source at reviewed head · Probe R7

Captured source
533:   // Evidence must come from the frame the turn was decided against.
534:   if (options.frame && turn.frameHash === undefined) {
535:     return {
536:       commands: [],
537:       rejected: turn.propositions.map((_, index) => ({
538:         index,
539:         reason: "turn has no frame identity to bind the supplied frame to",
540:       })),
541:     };
542:   }
543:   if (options.frame && turn.frameHash !== undefined && options.frame.hash !== turn.frameHash) {
544:     return {
545:       commands: [],
546:       rejected: turn.propositions.map((_, index) => ({
547:         index,
548:         reason: "frame does not match the frame the turn was decided against",
549:       })),
550:     };
Prompt for AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `packages/perception/src/proposals.ts` around lines 533-550, The whole-turn compiler checks frame.hash against turn.frameHash but does not compare frame.id with turn.frameId. propositionCommand checks both. Before the latest refactor the whole-turn compiler reached that identity check through the helper; it no longer does. Require both frame id and hash to match in the shared validation path. If content equivalence is intentionally sufficient, make that explicit across the contract and both APIs instead of silently changing it in one. Reproduce R7 using `/tmp/circe-pr2-evidence/probes.test.ts` at PR #2 head 91c123b06b177c6b943fe2ac2f0ffd1e8c2f9893. Keep valid requests working and run the full suite and typecheck after the change.