PR #2: evidence coverage still has two gaps

Changes requested

239 baseline tests pass · 46 prior reproductions pass · 3 new cases fail

2 P1 findings · all 3 new cases pass before this commit

Incremental +544 / -216 2cb82c8

The unified evaluator resolves the previously reproduced entry-point drift, and all 46 prior cases pass at 2cb82c8. I would still request changes for two evidence-coverage regressions, reproduced by three new tests.

Reviewed on 22 September 2026 at exact head 2cb82c8582ff2edf68fc8f47a5ec90d2aa1fada8, against the previous review head 34685ba. This incremental review covers the latest changes and their affected callers. PR #2 remained at that head during verification. Circe source files were not edited.

What is fixed

Both compiler entry points now select from evaluateTurn. Structural checks, exclusions, dependency rejection, evidence coverage and command construction share the same path. The previously rejected prerequisite can no longer authorize execution through the per-proposition helper.

The original evidence cases also pass. Revised target versions agree at validation and dispatch; retargeting retires the old target-only evidence; independent sequential targets no longer invalidate each other; and a genuine argument dependency outside the target set reaches every effect.

What remains

The rule that each ref has one owner conflates version storage with where the ref is used. B can be a target and an argument dependency of A's effect. The new filter makes B slot-owned globally, so A's effect keeps the B argument but loses B's precondition.

Revision has a related lifetime problem. It reconstructs the shared set from argument and constraint values, which can erase an explicitly declared read guard absent from those values. It also fails to transfer evidence from a replaced binding when the same ref remains in arguments. Both revision probes actually invoke after stale required evidence disappears from validation.

The smallest principled correction is to keep a canonical version per ref while tracking each use separately: slot target, shared argument dependency or explicit read guard. A revision may retire a use; effect splitting may select uses. Neither operation should erase a required version merely because another use exists or changed location. Fail closed if required evidence is unavailable. No new language rules or general scheduling framework are needed.

Verification

PR #2 passes 239 eval tests and typecheck. The independent suite passes all 46 prior cases and fails the three new cases. Those same three cases pass at 34685ba, establishing that they are regressions in this change. The new probe file passes strict typecheck.

Stack tip 9d78c7e passes 286 tests: 254 evals, 14 journal-file, 12 perception-jev and six inspector tests. Typecheck is clean. All 46 prior probes pass there, and all three new failures reproduce. The open PRs and declared bases match the reported stack. This does not constitute a full review of PRs #3-#6.

Recommendation

Fix F40 and F41 before merging PR #2. F38 and F39 retain their original bodies and are marked fixed for their original scenarios. The new findings preserve the distinction between successful repairs and newly introduced failures. No GitHub approval, comment or merge was submitted.

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

At the reviewed head this yields 46 passed and three failed. Downloaded configs contain local checkout paths; adjust aliases elsewhere.

3 new evidence probes Test config 46 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 F40. A ref used as both a target and an argument loses effect-level coveragepackages/core/src/runtime.ts:1121-1125logic data-integrity

The new ownership filter removes every bound ref from shared readDependencies. A ref can simultaneously be a target and a shared argument dependency. When the runtime splits a command into target effects, each effect keeps the same arguments but only its own binding expectations.

The reproduction compiles targets A and B with arguments.secondary = B. Both refs have frame evidence. After dispatch, the A invocation carries the B argument but has no precondition for B. Initial validation included B, but the executor cannot conditionally protect that read when it applies the A effect. This is a regression from 34685ba, where the same test passes.

Canonical storage of one version per ref is reasonable. It does not mean the ref is used in only one role or by only one effect. The preceding tests covered shared argument C outside the target set and therefore missed this overlap.

Fix direction. Track evidence usage separately from its canonical version. A shared argument dependency must be attached to every effect that consumes it even when the same ref is also bound as a target. Preserve the passing independent-target case where there is no actual shared dependency.

Source at reviewed head · Probe O1 · Related to F39

Captured source
1121:     const boundKeys = new Set(bindings.flatMap((binding) => binding.refs).map(entityRefKey));
1122:     const readDependencies = dedupePreconditions(
1123:       (command.expectedVersions ?? []).filter(
1124:         (precondition) => !boundKeys.has(entityRefKey(precondition.ref)),
1125:       ),
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 1121-1125, The new ownership filter removes every bound ref from shared readDependencies. A ref can simultaneously be a target and a shared argument dependency. When the runtime splits a command into target effects, each effect keeps the same arguments but only its own binding expectations. Track evidence usage separately from its canonical version. A shared argument dependency must be attached to every effect that consumes it even when the same ref is also bound as a target. Preserve the passing independent-target case where there is no actual shared dependency. Reproduce O1 using `/tmp/circe-pr2-round3-evidence/ownership.test.ts` at PR #2 head 2cb82c8582ff2edf68fc8f47a5ec90d2aa1fada8. Retain all 46 prior reviewer cases, including independent targets and genuine shared dependencies, and run the full suite and typecheck.
P1 F41. Revisions silently discard still-required read dependenciespackages/core/src/runtime.ts:1212-1231logic data-integrity

applyRevision now rebuilds readDependencies exclusively from refs found inside current arguments and constraints. It does not preserve an explicit command-wide read guard that has no corresponding argument. It also searches only the old shared list and latestSnapshots, ignoring evidence previously owned by a slot that the revision replaces. Missing versions are filtered out, after which commit checks coverage only for bound refs.

One reproduction proposes A with an explicit read dependency on C, changes C to a newer version, then revises only a scalar note. C disappears from validation and the request invokes despite the stale declared guard. A second starts with B as both target and argument, then retargets to A while retaining the B argument. The old B version exists on the replaced binding but is not transferred. With no kernel snapshot, B disappears from the read set and the request invokes even though B is stale.

Both cases pass on 34685ba and fail on 2cb82c8. The fix for obsolete slot evidence should retire evidence whose use ended, rather than discard explicit shared guards or evidence whose reference still appears in an executable argument.

Fix direction. Make revision update evidence from explicit dependency roles and the prior canonical evidence set, including replaced bindings. Preserve declared shared guards unless explicitly replaced or retired. Transfer versions for refs whose role changes, and reject missing required evidence instead of filtering it out. Validate coverage against the refs and guards the revised effect actually consumes.

Source at reviewed head · Probe O2, O3 · Related to F39

Captured source
1212:     const existingReadDependencies = new Map(
1213:       (intent.readDependencies ?? []).map((precondition) => [
1214:         entityRefKey(precondition.ref),
1215:         precondition,
1216:       ]),
1217:     );
1218:     const sharedRefs = refsInValues([argumentsNow, intent.constraints]).filter(
1219:       (ref) => !boundKeys.has(entityRefKey(ref)),
1220:     );
1221:     const observed = latestSnapshots(sharedRefs);
1222:     const readDependencies = dedupePreconditions(
1223:       sharedRefs.map((ref) => {
1224:         const existing = existingReadDependencies.get(entityRefKey(ref));
1225:         if (existing) return existing;
1226:         const snapshot = observed.get(entityRefKey(ref));
1227:         if (snapshot && snapshot.version.authority === ref.authority) {
1228:           return { ref: { ...ref }, token: { ...snapshot.version } };
1229:         }
1230:         return undefined;
1231:       }).filter((precondition): precondition is FrozenPrecondition => Boolean(precondition)),
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 1212-1231, applyRevision now rebuilds readDependencies exclusively from refs found inside current arguments and constraints. It does not preserve an explicit command-wide read guard that has no corresponding argument. It also searches only the old shared list and latestSnapshots, ignoring evidence previously owned by a slot that the revision replaces. Missing versions are filtered out, after which commit checks coverage only for bound refs. Make revision update evidence from explicit dependency roles and the prior canonical evidence set, including replaced bindings. Preserve declared shared guards unless explicitly replaced or retired. Transfer versions for refs whose role changes, and reject missing required evidence instead of filtering it out. Validate coverage against the refs and guards the revised effect actually consumes. Reproduce O2, O3 using `/tmp/circe-pr2-round3-evidence/ownership.test.ts` at PR #2 head 2cb82c8582ff2edf68fc8f47a5ec90d2aa1fada8. Retain all 46 prior reviewer cases, including independent targets and genuine shared dependencies, and run the full suite and typecheck.
Original review, baseline and finding bodies preserved

The following report describes 34685ba. Its original F38-F39 cases now pass. Open archive.

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
  • packages/core/src/runtime.ts +27 / -3
  • packages/core/src/types.ts +6 / -0
  • packages/evals/src/accepted-turn-pipeline.test.ts +1 / -1
  • packages/evals/src/accepted-turn-review2.test.ts +263 / -0
  • packages/perception/src/proposals.ts +114 / -32
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.