PR #6: no remaining merge blocker
F65-F70 are fixed. One nonblocking part of F71 remains in demo replay receipts.
1 open P2; 6 findings fixed. No GitHub review comments posted.
+1178 −147 e774311Reviewed on 23 September 2026 at e7743111bea382d634bd492a831f445f8e9833b8. Incremental scope 0d9e510..e774311, +189 −31. PR #6 remains open and is based on main.
Architecture and complexity
No remaining merge blockers. The unique option lookup fixes the host identity ambiguity, and the operation registry now handles ordinary replay, payload conflicts and caller-owned membership arrays. The changes are localized and use the existing host/executor contracts. A nonblocking receipt-identity gap remains in the demo replay branch. Returning the original recorded outcome would also make that branch simpler.
Verification
All 394 repository tests pass: 323 evals, 40 journal-file, 17 perception-jev and 14 inspector. Repository and probe typechecks, CLI and git diff --check pass. All 28 cases in the previous inspector review suite pass, including the cleanup assertion. The 140 prior independent probes retain 138 passes and two inherited status-getter failures. Two new probes have one pass and one failure for the remaining F71 gap. Combined independent runs: 167 passes and three failures across 170 cases; two failures are inherited and one is the nonblocking demo receipt issue.
Review JSONReview textNew probesNew resultsEarlier inspector probes394 repository testsRound-2 archive
Round-2 assessment and verification, historical
PR #6: two gaps remain
F66-F70 are fixed. F65 still permits an ambiguous option mapping; F71 covers the new operation registry.
2 open findings: 1 P1 and 1 nonblocking P2. 5 findings fixed. No GitHub review comments posted.
+1020 −147 0d9e510Reviewed on 23 September 2026 at 0d9e510b77a44c8f9f522a017876f27c60630453. Incremental scope d5723b4..0d9e510, +232 −73. PR #6 remains open, based on main.
Architecture and complexity
The fixes stay within the right modules. Atomic precondition checks run synchronously in the demo executor; the file-journal fixture now owns its handles; the unused compiler traversal is gone. A small operation map is appropriate. Two contracts remain incomplete: option-to-entity identity must be unique, and an operation record must preserve the original request and outcome. These need local checks and snapshots, not more architecture.
Verification
All 390 repository tests pass: 323 evals, 40 journal-file, 17 perception-jev and 10 inspector. Typecheck, probe typecheck, CLI and git diff --check pass. All nine original behavioral probes and the cleanup assertion over ten retained tests pass, 19 tests total. The 140 prior probes retain 138 passes and two inherited status-getter failures. Five new probes have four failures and one passing control; the failures support the remaining F65 gap and new F71.
The positive new control confirms recorded success reconciles correctly and a fresh demo world returns UNKNOWN. Original A/B execution, failed-perception containment and completed pipeline retry still pass.
Review JSONReview textNew probesNew resultsOriginal probes rerun390 repository testsRound-1 archive
Round-1 assessment and verification, historical
PR #6 needs changes
The fixed A/B demo passes, but the host widens targets and the executor invents guarantees under drift and uncertain dispatch.
6 open findings: 3 P1 and 3 P2. One P2 is nonblocking dead-code cleanup. No GitHub review comments posted.
+861 −147 d5723b4Reviewed on 23 September 2026 at d5723b4e88eff95624cd33f0b14ecbbac8d7cce5, based on 35de6de98ea0e224eec011c0a77a97cb44ee50bc. PR #5 was merged at its approved head cdbb1af. PR #6 rebased cleanly, is patch-identical to its original commit, and was pushed with an explicit lease. Its base is now main; PR #6 remains open.
Architecture and complexity
The inspector is a reasonable host: it observes authorities, assembles context, asks the existing workflow, compiles typed commands and injects a journal. It does not introduce a second kernel. The happy path and answer-inversion test provide useful wiring evidence. However, the target adapter silently assumes a two-candidate world, while the demo executor promises guarantees it does not implement. Those assumptions make this an unreliable safety demonstration under scope expansion, post-validation drift and uncertain dispatch. Tighten those local contracts rather than introducing another framework.
This remains a scripted integration demonstration. It does not interpret arbitrary utterances, call a live Jev SDK, measure held-out semantic accuracy, or prove external work was resumed. The demo records invocations and returns success; the CLI phrase “intended task resumed” should be read within that demo scope. Real semantic interpretation and calibration remain deferred, as stated in the PR.
Verification
Typecheck and all 386 repository tests pass: 323 evals, 40 journal-file, 17 perception-jev and 6 inspector. The CLI exits 0 on the fixed A/B scenario. The 140 prior independent probes have 138 passes and the same two inherited status-getter failures. Nine new behavioral probes have six failures and three passing controls. Re-running the six retained inspector tests with an afterAll cleanup assertion passes those six tests but exposes four leaked descriptors, producing one additional suite failure. The new probes typecheck. git diff --check passes.
The passing controls cover the ordinary A/B pipeline, failed perception causing no effects, and a completed pipeline retry producing no second invocation. F67 also has an actual recovery/reconciliation reproduction, not only a direct mock call.
Review JSONReview textBehavioral probesFD cleanup probeNew resultsPrior probes386 repository testsCLI output
Walkthrough
PR #6 replaces the inspector CLI with a fixed end-to-end scenario and adds the host pipeline, a demo authority/executor, shared scenario mapping and six integration tests.
- Observe actual authority snapshots and assemble a bounded context frame.
- Run the scripted Jev-backed decision workflow and derive exclusion/include propositions from accepted answers.
- Compile typed commands and execute them through the kernel and a file journal.
- Reopen the journal to compare recovered effect outcomes with the live run.
- A new collectRefKeys helper in shared perception is unused.
Round 2 changes
F66-F70 fixed. F65 partially fixed but still open for non-unique local option IDs. New nonblocking F71 covers operation record identity, replay and ownership.
The new domain guard rejects C and checks the number of remaining entities, but it does not require each option to identify a unique canonical ref. R2-4 assembles two observed candidates with local ID B under different authorities and no A. Both pass the option-membership check. refForChoice picks the first B, and the other B becomes an executable resume target. Keep F65 open: require an unambiguous A/B option-to-ref mapping, or reject this incompatible scope.
Round 3 changes
F65 fixed; all original and round-2 reproductions pass. F71 retains one nonblocking receipt-identity edge case. No new finding IDs or merge blockers.
F71 is substantially fixed: replay after drift, conflicting target payloads and caller-mutated effect membership now pass. One identity gap remains in the known-key branch. Its fingerprint omits effect identity, and its success receipt uses the incoming request.effectId. R3-1 repeats a recorded key and payload with a new effect ID and receives a success receipt for that never-recorded effect. Reject changed effect membership or return the original recorded receipt; do not mint a receipt from the replay request. This remains a nonblocking P2 in the demo executor.
Files changed at round 1
| File | Change |
|---|---|
| apps/inspector/package.json | +9 −3 |
| apps/inspector/src/cli.ts | +147 −144 |
| apps/inspector/src/demo.ts | +134 |
| apps/inspector/src/pipeline.test.ts | +225 |
| apps/inspector/src/pipeline.ts | +147 |
| apps/inspector/src/scenario.ts | +163 |
| packages/perception/src/proposals.ts | +21 |
| pnpm-lock.yaml | +15 |
Issues: 1 open, 6 fixed
| ID | Priority | Type | Finding |
|---|---|---|---|
| F71 | P2 | logic | Store immutable request identity and the original operation result |
| F65 · fixed | P1 | logic | Constrain “the other one” to the bounded decision domain |
| F66 · fixed | P1 | logic | Honor the atomic precondition contract at invocation |
| F67 · fixed | P1 | logic | Reconcile from recorded executor outcomes |
| F68 · fixed | P2 | logic | Decode canonical identity components before returning refs |
| F69 · fixed | P2 | logic | Close writable test journals before deleting their directories |
| F70 · fixed | P2 | style | Remove the unused recursive reference walker |
Data flow
Authority observations → context frame → accepted workflow decisions → host target mapping → compiler → kernel validation → demo executor → journal → recovery / reconciliation
F65 sits at the host target mapping. F66-F67 sit at the executor boundary. F68 sits at observation identity reconstruction.
Prompt for all review comments · Ctrl+`
Prompt to re-run review · Ctrl+Space
Round-2 finding records
Historical round-2 F65 update; fixed in round 3
The new domain guard rejects C and checks the number of remaining entities, but it does not require each option to identify a unique canonical ref. R2-4 assembles two observed candidates with local ID B under different authorities and no A. Both pass the option-membership check. refForChoice picks the first B, and the other B becomes an executable resume target. Keep F65 open: require an unambiguous A/B option-to-ref mapping, or reject this incompatible scope.
Original finding records
Fixed in e774311 P1 F65. Constrain “the other one” to the bounded decision domainapps/inspector/src/scenario.ts:90-107logiccorrectness
The scenario asks a referent decision whose options are A, B and none, but propositionsFromDecisions builds the executable include set from every candidate in the frame. Subtracting the risky choice from that larger set is only equivalent to “the other one” when the frame contains exactly the intended pair.
P6-2 adds an unrelated task C to the pipeline scope while leaving the accepted decisions unchanged: sketchy=B and operation=task.resume. The run reports RESOLVED and the pipeline invokes both A and C. C was never in the bounded referent question. The existing answer-inversion test proves the A/B case, but it does not establish that the host preserves the decision domain or singular cardinality.
For this deliberately fixed scenario, reject a scope that cannot identify a unique remaining member of the intended pair. Alternatively derive decision options and target selection from an explicitly identified domain. Do not broaden a singular request to every retrieved candidate. This needs a small contract check, not an English interpreter.
Fix direction. Bind the reference scenario to a unique, explicit A/B candidate domain and require one remaining target. Reject or clarify an incompatible scope before emitting executable propositions.
Evidence: P6-2
90: const riskyRef = refForChoice(frame, risky);
91: if (!riskyRef) return [];
92: const scopeRefs = frame.candidates.map((candidate) => candidate.ref);
93: return [
94: {
95: act: "request",
96: operation: "exclude",
97: refs: [riskyRef],
98: polarity: "exclude",
99: decisions: [sketchyTrace.id],
100: },
101: {
102: act: "request",
103: operation: "reference",
104: refs: scopeRefs,
105: polarity: "include",
106: capability,
107: decisions: [operationTrace.id],Prompt for AI agents
Fixed in 0d9e510 P1 F66. Honor the atomic precondition contract at invocationapps/inspector/src/demo.ts:105-111logiccorrectness
taskResume advertises conditionalApply="atomic", but the demo executor ignores InvokeRequest.preconditions and immediately records every binding as invoked. The earlier authority validation cannot provide atomic execution: the world may move after that validation and before the executor applies the request.
P6-3 wraps the authority validator so it returns current for A version 1, then changes A to version 2 before returning control to the runtime. The executor still records A and returns SUCCEEDED. The retained drift test moves the world before validation, so it exercises only the earlier check and misses this boundary.
The inspector is intended to demonstrate the full contract, and its executor is the part responsible for this guarantee. Compare all frozen preconditions against the demo world immediately before recording or applying any effect, with no asynchronous gap. On mismatch, fail without touching any target. If the demo does not implement that contract, it must not advertise atomic application.
Fix direction. Check every frozen precondition in the demo executor before any application; return a stale or invalidated failure on disagreement. Keep the check and apply synchronous within the in-process world.
Evidence: P6-3
105: invoke: async (request: InvokeRequest) => {
106: for (const binding of request.bindings) {
107: for (const ref of binding.refs) invoked.push(ref);
108: }
109: return {
110: ok: true,
111: status: "SUCCEEDED",Prompt for AI agents
Fixed in 0d9e510 P1 F67. Reconcile from recorded executor outcomesapps/inspector/src/demo.ts:109-120logicdata-integrity
The demo reconciler returns SUCCEEDED for every receipt and keeps no operation records. A receipt proves that the runtime attempted dispatch; it does not prove that the executor applied the operation. The executor also ignores repeated idempotency keys while advertising runtime-key idempotency.
P6-8 runs the real pipeline with a dispatch wrapper that throws before calling the demo executor. The runtime correctly journals UNKNOWN, and world.invoked is empty. A fresh runtime recovers that journal and calls the demo reconciler, which changes the effect to SUCCEEDED even though no invocation occurred. P6-4 confirms the same result for a completely unknown receipt. P6-9 invokes the same request twice and records A twice.
Keep a small operation-result map in the demo world, keyed and checked by the dispatched operation identity. Known duplicate keys should return the recorded result without another application. Reconciliation must return UNKNOWN for absent or mismatched operations. No durable external executor service is needed here; after a fresh demo world loses its operation records, UNKNOWN is the truthful result.
Fix direction. Record applied operations and results in the demo executor, deduplicate known idempotency keys, and reconcile only matching recorded outcomes. Unknown receipts must remain UNKNOWN.
Evidence: P6-4, P6-8; P6-9 idempotency control
109: return {
110: ok: true,
111: status: "SUCCEEDED",
112: receipt: {
113: effectId: request.effectId,
114: executor: "demo-executor",
115: idempotencyKey: request.idempotencyKey,
116: dispatchedAt: new Date(0).toISOString(),
117: },
118: };
119: },
120: reconcile: async () => ({ status: "SUCCEEDED" as const }),Prompt for AI agents
Fixed in 0d9e510 P2 F68. Decode canonical identity components before returning refsapps/inspector/src/demo.ts:66-70logiccorrectness
The authority stores entities under entityRefKey, then splits that escaped key on pipes and treats each component as the original string. entityRefKey escapes colons, pipes and percent signs. Splitting identifies component boundaries but does not undo those escapes.
P6-1 observes the valid ID A:pipe|percent%. The returned ID is A%3Apipe%7Cpercent%25, a different entity. When observation ingestion keys it again, it is escaped a second time. The originally requested reference no longer matches its evidence, causing valid targets to disappear or lack usable version evidence.
Use the existing decodeIdentityComponent helper for all four components, or retain the original EntityRef alongside the demo entity. This is an identity fix; the simple A/B fixtures currently hide it.
Fix direction. Decode all split components with the core identity helper, or store original refs instead of reconstructing them from keys.
Evidence: P6-1
66: const [authorityId = "demo-tasks", namespace = "tasks", type = "task", id = key] =
67: key.split("|");
68: return {
69: ref: { authority: authorityId, namespace, type, id },
70: version: { authority: authorityId, token: entity.version, observedAt: new Date(0).toISOString() },Prompt for AI agents
Fixed in 0d9e510 P2 F69. Close writable test journals before deleting their directoriesapps/inspector/src/pipeline.test.ts:29-49logicstability
The input helper creates a FileJournal but returns it through the narrower InteractionJournal field. Tests pass it inline to the pipeline and then remove the temporary directory in finally without closing the writable handle. The pipeline correctly treats its injected journal as caller-owned, so it cannot clean up the lost reference for these tests.
The independent cleanup probe imports and runs all six retained inspector tests, then inspects /proc/self/fd. Four descriptors still point to deleted circe-inspector journal files. Linux permits the deletion and the tests pass, but repeated runs leak descriptors and hold the unlinked files until process exit. Read-only recovery handles are lazy and do not account for these four leaks.
Retain each FileJournal in the test fixture and close it in finally before rmSync. Apply the same explicit ownership pattern to any handle that can append. The CLI closes its writable journal already; this finding concerns the test fixture.
Fix direction. Have the fixture retain and expose the FileJournal or a cleanup callback; close writable journals before removing temporary directories in every finally block.
Evidence: fd-cleanup.test.ts afterAll assertion
29: function input(
30: journalPath: string,
31: world: ReturnType<typeof createDemoWorld>,
32: answers: { sketchy: string; operation: string },
33: overrides: Partial<InteractionPipelineInput> = {},
34: ): InteractionPipelineInput {
35: return {
36: interactionId: "interaction:inspector",
37: observationId: "event:inspector",
38: utterance: "Leave the sketchy auth one alone and continue the other one.",
39: now,
40: refs: [refA, refB],
41: capabilities,
42: workflow: difficultUtteranceWorkflow,
43: backend: createScriptedBackend(answers),
44: policy: ACCEPTANCE_POLICY_V1,
45: propositions: propositionsFromDecisions,
46: journal: createFileJournal(journalPath),
47: authorities: world.authorities,
48: executors: world.executors,
49: ...overrides,Prompt for AI agents
Fixed in 0d9e510 P2 F70. Remove the unused recursive reference walkerpackages/perception/src/proposals.ts:298-316stylemaintainability
The only substantive shared perception change in this PR adds collectRefKeys, but nothing calls it outside its own recursive body. The active argument exclusion and evidence paths already use argumentRefsOf. This new traversal contributes no protection to the inspector pipeline.
Keeping an unused second implementation makes the security-sensitive argument handling harder to assess and suggests a check is active when it is not. The new helper also lacks the safeguards of the existing argument validation path. It is dead code, so that difference is not currently an execution defect.
Delete the unused helper and the stray blank line added in compileSemanticTurn. The inspector PR can then leave this shared compiler file unchanged. This cleanup is nonblocking, but it directly reduces unnecessary code in an already reviewed boundary.
Fix direction. Remove collectRefKeys and its incidental whitespace change; retain the existing argumentRefsOf-based validation.
Evidence: Static reference search: only definition and self-calls
298: /** Entity refs hidden anywhere in opaque argument values. */
299: function collectRefKeys(value: unknown, found = new Set<string>()): Set<string> {
300: if (Array.isArray(value)) {
301: for (const item of value) collectRefKeys(item, found);
302: return found;
303: }
304: if (typeof value !== "object" || value === null) return found;
305: const record = value as Record<string, unknown>;
306: if (
307: typeof record.namespace === "string" &&
308: typeof record.type === "string" &&
309: typeof record.id === "string" &&
310: typeof record.authority === "string"
311: ) {
312: found.add(entityRefKey(record as { namespace: string; type: string; id: string; authority: string }));
313: }
314: for (const nested of Object.values(record)) collectRefKeys(nested, found);
315: return found;
316: }
P2 F71. Store immutable request identity and the original operation resultapps/inspector/src/demo.ts:120-155logicdata-integrity
The new operation map stores effect IDs and refs, but no request identity or result. invoke validates current preconditions before checking the map; when a key already exists, it skips application and synthesizes success for the incoming request. These paths do not implement replay of a recorded operation.
R2-1 applies A at version 1, changes the world to version 2, then repeats the identical request. The repeated call returns STALE_REFERENCE despite the recorded success. R2-2 reuses the same key and effect ID with target B: the executor returns success even though only A was applied. Reconciliation and invoke can therefore disagree about the same operation, or success can be attached to a different payload.
R2-3 exposes a related ownership gap: request.effectIds is stored by reference. Appending never-applied to that array after invocation causes reconciliation to report SUCCEEDED for that effect. The record must be a detached account of what happened, rather than mutable caller data.
Keep the small in-process map, but store a detached operation fingerprint, effect membership and original result. On a known key, check identity and return the recorded result before consulting current world versions; reject conflicting payloads. Check live preconditions only for a new operation. This is a nonblocking P2 in the demo, and needs no additional service or generic retry framework.
Fix direction. Snapshot request identity, effect IDs and the applied result. Resolve known matching keys from that record before revalidating world state; reject conflicting key reuse and detach stored arrays.
120: for (const precondition of request.preconditions) { 121: const entity = world.get(entityRefKey(precondition.ref)); 122: if (!entity || entity.version !== precondition.token.token) { 123: return { 124: ok: false, 125: failure: { 126: kind: "STALE_REFERENCE", 127: detail: `precondition for ${entityRefKey(precondition.ref)} is no longer current`, 128: }, 129: }; 130: } 131: } 132: // runtime-key idempotency: a duplicate dispatch of the same operation 133: // returns the recorded result without applying the targets again. 134: const existing = operations.get(request.idempotencyKey); 135: if (!existing) { 136: const applied: EntityRef[] = []; 137: for (const binding of request.bindings) { 138: for (const ref of binding.refs) { 139: invoked.push(ref); 140: applied.push(ref); 141: } 142: } 143: operations.set(request.idempotencyKey, { 144: effectIds: request.effectIds ?? [request.effectId], 145: refs: applied, 146: }); 147: } 148: return { 149: ok: true, 150: status: "SUCCEEDED", 151: receipt: { 152: effectId: request.effectId, 153: executor: "demo-executor", 154: idempotencyKey: request.idempotencyKey, 155: dispatchedAt: new Date(0).toISOString(),Prompt for AI agents