# PR #4 review round 5

Ready to merge at 00a00aec2ddb6fab754f3c461bd208e86c3addfb. All F57-F60 findings verified fixed. No new blocker found in the latest diff and affected paths. PR #4 has not been merged.

363 repository tests and 122 independent probes pass. Repository and probe typecheck pass.

Verified fixed. validateEntitySnapshot now checks the owned ref, version, attributes container and observedAt fields. Authority-token validation is shared with preconditions, and matched refs are validated when present. M1 and M2 now reject the malformed snapshot with journal context before restoration or proposal execution. M3 still restores a populated valid view, uses its evidence, executes once, and replays after restart without reinvocation. All earlier decoding probes pass.

The final fix adds 16 net production lines, shares authority-token validation and adds a focused snapshot validator. No larger persistence framework is needed. Multi-process locking and compaction remain deferred; tests do not physically simulate power loss.

Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Sidecar: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.json
HTML: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.html

No open findings remain at 00a00aec2ddb6fab754f3c461bd208e86c3addfb. All F57-F60 findings are verified fixed. No fix work is requested by this review.

---

# PR #4 review round 4

Reviewed 9fbc00307a7e01dfa348228e8065b745c670fc7b. F57, F58, F60 fixed. One P2 remains in F59.

361 repository tests and 119 earlier independent tests pass; new cases: 2 fail, 1 passes. Typecheck passes.

Partially fixed. All previous malformed collection and binding probes now pass. The new discourse-view decoder checks that each entities member is an object but does not validate EntitySnapshot fields. M1 loads a view with entities: [{}] without error. M2 restores that view and issues a real typed proposal; latestSnapshots calls entityRefKey on the missing entity.ref and throws Cannot read properties of undefined (reading authority). M3 uses a populated valid view, restores version evidence, executes successfully, then restarts again and replays without a second invocation. This is a malformed persisted-data gap, not a normal runtime-write failure.

Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Sidecar: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.json
HTML: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.html

In `packages/core/src/types.ts` around lines 977-984, validate the EntitySnapshot members inside persisted discourse views. The current `journalRecord(entity, ...)` only proves the member is an object, so `{}` crosses the journal boundary and a later proposal crashes in `latestSnapshots` while reading its missing ref. Reuse `validateEntityRef` and the owned authority-token checks for ref and version, validate the snapshot container fields, and leave application attribute values opaque. M1 and M2 in `/tmp/circe-pr4-evidence/disk-round4.test.ts` reproduce acceptance and the downstream crash. Keep M3 passing: a populated valid view must restore evidence, execute once, and replay after restart without another invocation. Preserve all 119 prior independent checks; do not add a special case for an empty object.

---

# PR #4 review round 3

Reviewed 5f3f755160b89d5283b04c371116d8576cccdc46. F57 and F58 fixed. F59 partially fixed; new P2 F60. No open P1 findings. Recommend correcting decoder issues before merge.

354 repository tests and 112 earlier independent tests pass; new cases: 5 fail, 2 pass. Typecheck passes.

Partially fixed. All previous nested-record cases now reject. However, validateDiscourseSnapshot checks only that scopes, views and referents are arrays; each accepts [null]. L2 then reaches runtime restoration and throws Cannot read properties of null (reading id). L3 removes only expectations from an otherwise valid intent binding and the decoder accepts it, although live commit iterates binding.expectations. These are deliberate malformed-file probes of owned kernel data, not normal-writer failures.

DispatchReceipt declares idempotencyKey as optional, but validateReceipt now requires a string unconditionally. This makes the persisted decoder stricter than the exported InteractionJournal contract. A receipt identified by its externalOperationId is a valid typed value without an idempotency key.

L1 constructs a dispatch-acknowledged entry using InteractionLogEntry with no unsafe type assertion. The journal successfully appends it. The next ordinary append rereads that record, rejects it as corrupt because receipt.idempotencyKey is missing, and poisons the handle. A keyed receipt follows the same path successfully in L5.

This is a regression introduced by the new validator. The kernel's current invoke path supplies a key, which explains why runtime fixtures pass, but the public journal port accepts the wider declared receipt type. A valid acknowledged record must remain readable and appendable. Validate optional fields when present instead of silently narrowing the port contract.

Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Sidecar: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.json
HTML: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.html

In `packages/core/src/types.ts` around lines 812-817, 903-925, finish the owned nested-data checks behind `decodeJournalEntry`. Array shape alone does not validate the scopes, views and referents that restoration dereferences, and a SlotBinding needs expectations and origins that a FrozenSlotBinding does not. L2 and L3 in `/tmp/circe-pr4-evidence/disk-round3.test.ts` cover null discourse members and a binding missing expectations. Use distinct validators for the two binding types and validate collection members with the corresponding owned structure validators, including result arrays where applicable. Preserve line-numbered corruption errors and keep application payloads opaque. Keep all 112 earlier probes and the valid snapshot control passing; avoid per-example rejection rules.

In `packages/core/src/types.ts` around lines 878-883, align `validateReceipt` with the existing `DispatchReceipt` type, whose `idempotencyKey` is optional. L1 in `/tmp/circe-pr4-evidence/disk-round3.test.ts` appends a fully typed receipt without that key and then fails on the next ordinary append because the decoder rejects the earlier valid record. Validate the key only when present, without changing the public type to hide the mismatch. Preserve required receipt validation and test read/reopen/continued append with both optional-key omission and a populated key. L5 provides the keyed control. Keep the existing runtime round trips and all prior independent checks passing.

---

# PR #4 review round 2

Changes requested at 1ce57ae49ac12407a8cfa8e804484fd9997358e8. F57 and F59 remain partially fixed; F58 is fixed.

348 repository tests and all 106 prior independent tests pass; new cases: 5 fail, 1 passes. Typecheck passes.

## F57 open

Partially fixed. J1 and J2 now pass when the handle is opened after the torn tail exists. K1 opens two handles on a clean file, injects a partial write and failure through the first, then appends through the surviving handle. Its constructor cached no repair; normalizeTail returns before rereading. The append succeeds but its record disappears on reopen. All operations are sequential in one process. K4 passes when both handles were opened after the tail was already torn.

## F58 fixed

Verified fixed. directorySynced starts false for a new journal object, so reopening after a directory-fsync failure establishes directory durability on the next append. Original J3 passes, along with normal-create and reopen controls.

## F59 open

Partially fixed. Complete invalid top-level records and unknown kinds now reject, and the original J4/J5 cases pass. The new decoder only checks that nested payloads are objects. K2 admits a transaction with only id, an empty intent and an empty command result. K3 feeds the admitted transaction to recoverState and gets tx.effects is not iterable instead of a line-numbered journal corruption error. These are deliberately malformed files testing the documented decoding boundary, not a claim that normal runtime writes produce them.

Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Sidecar: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.json
HTML: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.html

In `packages/journal-file/src/index.ts` around lines 160-165, do not use constructor-time tailRepair absence as proof that the current append boundary is clean. Another supported handle can leave a partial write after this handle was created. Before accepting the next append, inspect the current boundary or enforce equivalent shared invalidation. Preserve prior complete records and the next acknowledged record, or fail closed before writing. K1 in `/tmp/circe-pr4-evidence/disk-round2.test.ts` reproduces the failure using sequential real-file writes and one injected write failure. Keep K4, which verifies that a later handle does not truncate another handle's successful repair, and all earlier disk checks passing.

In `packages/core/src/types.ts` around lines 785-881, complete `decodeJournalEntry` for the owned nested data consumed by recovery. Checking that transaction, intent or result is a non-null object does not establish the InteractionLogEntry type. Reject missing or wrongly typed required nested fields with the journal loader's line-numbered corruption context; keep arbitrary application payloads opaque. K2 and K3 in `/tmp/circe-pr4-evidence/disk-round2.test.ts` cover a transaction without effects, an empty intent, an empty command result and the resulting recovery crash. Reuse compact validators for owned structures instead of adding per-example exceptions or a second divergent schema. Keep valid runtime journal round trips and the original rejection cases passing.

---

# Circe PR #4 file journal review

Changes requested. Reviewed 7f1beb3ce6476c50168ad07158572ee149ec4912. PR #3 merged; PR #4 rebased onto main without conflicts.

337 repository tests and 95 prior independent probes pass. New disk suite: 8 failures, 3 passing controls. Typecheck passes.

## F57 P1 Repair the append boundary after tolerating a torn tail

packages/journal-file/src/index.ts:93-96

The constructor validates the file with parseEntries, but discards the location of the tolerated final fragment. handle then opens the same bytes with O_APPEND. Ignoring a broken tail while reading does not make the file safe to append to.

J1 loads an old valid record followed by an incomplete JSON fragment, then appends a new record successfully. Without a trailing newline, the new record joins the fragment and disappears on the next read. With a trailing newline, the fragment becomes an interior hole and reopening rejects the entire journal. J2 starts with a complete final JSON record missing only its newline; the next append concatenates two JSON objects and both records disappear.

These are supported recovery inputs, not concurrent-writer scenarios. The current tests stop after reading the tolerated tail. A recovery adapter must preserve the next acknowledged append too. Otherwise a successful write-ahead append can be missing when the kernel next reconstructs state.

Fix: Retain the last safe byte boundary and normalize the tail before accepting another append, with the repair included in the durability boundary. Alternatively fail closed until an explicit repair completes. Preserve complete records and ensure a final complete record without a newline stays separate from the next record.

J1, J2: three failing cases

## F58 P1 Do not infer directory durability from file existence

packages/journal-file/src/index.ts:90-96

directorySynced is initialized from existsSync. That proves the name is currently visible, not that a previous creation synced its directory. A failed first append can leave a complete, file-synced journal behind before the directory sync succeeds.

J3 injects a failure only into the first parent-directory fsync. The initial append correctly throws and poisons its handle. After close and reopen, a second append returns success, but no parent-directory fsync is attempted because the path now exists. The reopened handle has forgotten the unfinished creation durability step.

File fsync does not establish durability of its directory entry. A later successful append therefore lacks the documented guarantee that the filename survives power loss. This probe establishes the missing syscall after a real-file failure sequence; it does not simulate a physical power loss.

Fix: Establish parent-directory durability on the first durable append of a newly opened journal even when its path already exists, or use a protocol that proves creation durability. An existence check alone cannot carry that proof across handles.

J3: one failing case

## F59 P2 Separate torn JSON from invalid journal records

packages/journal-file/src/index.ts:64-78

The catch that tolerates a torn final JSON fragment also catches the loader's own record-shape errors. A complete final null, number or unrelated object is silently discarded. Separately, an object with any string kind is cast to InteractionLogEntry without checking the required data for that entry.

J4 writes a valid record followed by each of null, 42 and an unrelated object, all newline-terminated. All three open successfully instead of reporting corruption. J5 places an effect-frozen object with no effect in the interior of the file; it is also accepted. For the matching interaction, the core recovery reader accesses entry.effect.id directly at journal-recovery.ts:172.

This contradicts the adapter's stated refusal of non-entry lines and moves malformed persisted data past the typed journal boundary. These probes deliberately corrupt files; they do not claim the current runtime normally emits those records. Keep syntax truncation tolerance distinct from record validation, and report a useful line-numbered corruption error.

Fix: Catch JSON syntax errors separately from entry decoding. Reject complete invalid records at any position and validate required fields of recognized entry kinds before exposing InteractionLogEntry values. Share a small decoder at the journal boundary if appropriate; avoid maintaining a second divergent journal schema.

J4, J5: four failing cases

Filesystem reference: https://man7.org/linux/man-pages/man2/fsync.2.html

Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Sidecar: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.json
HTML: /home/abstergo/Documents/reviews/Circe-file-journal-abbyview.html

In `packages/journal-file/src/index.ts` around lines 42-82, 93 and 126-133, make tolerated tails safe for subsequent writes. `parseEntries` currently drops the final invalid line in memory while `append` writes after its unchanged bytes. Reproduce J1 with and without a trailing newline and J2 with a complete record lacking only its newline. Preserve all previously accepted records and every successful subsequent append. Keep the safe byte offset or equivalent tail state, repair or reject before appending, and include any repair in the durability boundary. Do not introduce a general storage framework. Retain the short-write, failure-poisoning and normal-reopen controls.

In `packages/journal-file/src/index.ts` around lines 90-96 and 133-136, remove the assumption that an existing path means its directory entry was durably created. J3 injects a directory-fsync failure after the first file fsync, closes the failed handle, reopens the existing path and appends successfully without another directory sync. Make the reopened durable writer establish the missing parent-directory durability before reporting success. A first-append sync per opened journal is a small solution; avoid persistent bookkeeping just to remember one fsync. Retain poisoning on failures and test both normal creation and reopening after an injected directory-sync failure.

In `packages/journal-file/src/index.ts` around lines 64-78, distinguish incomplete JSON from invalid journal data. The same catch currently swallows final-line validation errors, and the string-kind check admits recognized records without required payloads. Keep the intended torn-tail recovery, but reject complete invalid entries with line-numbered errors. J4 covers final null, 42 and an unrelated object; J5 covers an interior effect-frozen record without an effect, which the recovery reader assumes exists. Add focused decoding at the persistence boundary and an explicit policy for unsupported kinds. Prefer sharing journal schema knowledge over duplicating recovery logic. Keep valid disk recovery and all earlier review probes passing.
