fix(sessions): invalidate exact Cursor parent rewrites - #947
Conversation
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
|
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
|
Review against tip f81c9f9. Verdict: needs-fix (correctness by deleting the optimisation the module exists for).
Sequencing: after #948 (both in tracedecay-sessions; this rewrites 463 lines that #948 never touches). |
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…sor/fix-cursor-parent-cache-f604
…sor/fix-cursor-parent-cache-f604
`jsonl_prefix_digest(file, verified_cursor)` ran on every lookup, so each cache hit re-read and re-hashed the whole verified prefix — the exact per-subagent-batch re-read from byte zero this module's doc says it exists to stop. The revision it already stores answers the question first: when identity, length and the change token (ctime on Unix, which is what catches a same-length same-mtime rewrite) are all unchanged and the entry covers the whole file, nothing can have been appended or rewritten, so the cached model is served without touching the file. The digest stays as the fallback for a changed token, which is the only case it can still tell apart — and on Windows, where the token is last_write_time alone, that fallback is what catches a preserved-timestamp rewrite. Discarding a whole scan when the parent changed under it went too far. A live Cursor parent is appended to while it is being read, which is the normal case for the sessions this index serves; the appended bytes lie past everything the scan verified, so the verified prefix is committed and the next pass reads only the delta. Sending the next call back to byte zero instead let it lose the same race again. Only a replaced file or a truncation invalidates what was read, and `scanned_prefix_survives` names exactly that. Prefix-validation reads are now charged to the scan receipt as their own field, so `unchanged_parent_repeated_misses_parse_bytes_once` fails if the fast path stops serving without I/O, and the gauge no longer under-reports the bytes the lookup actually read. `TEST_ANCHOR_WINDOW_BYTES` named a deleted concept and now names what it sizes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Review findings applied on top of
1 + 2 (digest on every lookup, 3 (whole scan discarded on a mid-scan append). Replaced with 4. 5. Gates run, results below. One addition beyond the findings, needed to make finding 1 testable: prefix-validation reads were invisible to Two new tests, Gates, all on the pushed tip
|
ScriptedAlchemy
left a comment
There was a problem hiding this comment.
Architecture review — correctness direction accepted; HOLD for bounded-work and Windows proof
Reviewed 43d5fc44d7d0a9354d7b9546837472889a3f956d; source review only, exact-head CI pending. Binding metadata, parsing and revalidation to one opened handle and invalidating equal-length/equal-mtime rewrites is the right fix. Keep the full-prefix evidence; do not restore the trailing-4KiB heuristic or claim mtime alone proves unchanged content.
P1 performance acceptance: the previous no-I/O cache hit is removed. ParentDispatchIndex::lookup now content-validates the entire cached prefix before every reuse, so repeated lookups over a large parent transcript perform repeated O(prefix) reads even when parsing is delta-only. Attach the benchmark's actual bytes-read, wall time and corpus sizes, including a large parent queried by many subagents, not just a benchmark command. The new prefix_digest_bytes counter must expose that cost. Batch lookups within one verified ingest/read snapshot or use a genuinely authoritative change witness where available; do not cache across unverified rewrites. Keep this work off a latency-sensitive runtime worker and avoid holding an unrelated global registry lock across it.
P1 correctness acceptance: preserve equal length and exact mtime in Windows replacement tests, rewrite only the early prefix while the final 4KiB remains identical, and verify a concurrent mutation during validation/parse refuses or retries without committing mixed models. Append parsing must still consume only new records after verifying the prefix.
The five unrelated failures listed in the body need baseline comparison, not dismissal as 'environment-sensitive'; they must not be introduced by shared native identity helpers. No source-key migration or weakening of #880 admission is required for this cache correction. Target #707 only.
…plan-reopened' into cursor/fix-cursor-parent-cache-f604
…plan-reopened' into cursor/fix-cursor-parent-cache-f604
ScriptedAlchemy
left a comment
There was a problem hiding this comment.
System-level re-review at 43d5fc44d — Windows exact-rewrite hole in the fast path
I re-read parent_dispatch_index.rs::plan_lookup and the actual JsonlFileChangeToken definitions in runtime/source/jsonl.rs. The source needs a stronger correction than the earlier general performance comment.
P1 — the zero-content-read fast path is unconditional on Windows
plan_lookup returns the cached model when entry.revision == revision && verified_cursor == revision.len, before calling jsonl_prefix_digest. The comment says a Windows rewrite preserving last-write time falls through to the digest, but there is no platform/proof condition implementing that statement. On Windows, JsonlFileChangeToken contains only last_write_time. An in-place complete-file rewrite preserving length and that timestamp therefore keeps native file identity, length and change token identical, and this branch returns the stale model. Opened-handle identity distinguishes replacement files, not writes to the same file.
Permit this shortcut only with an adequate change witness; otherwise perform the exact prefix validation. Do not treat a Unix ctime-containing token and a Windows last-write-only token as the same evidence. Add a Windows regression that primes a newline-terminated file to EOF, changes an early model field to an equal-length value, restores the precise timestamp, closes the writer, and queries again. Assert the returned model is new and the validation receipt accounts for the content reads. Keep the unchanged trailing-4KiB case. No Windows execution is claimed here; this follows directly from the inspected predicate and token fields.
Correction to my earlier wording: the currently inspected source does have a no-content-read cache-hit path, so 'every lookup hashes the full prefix' is not an accurate unconditional description. The problem is whether that shortcut has the proof the platform can actually supply.
Keep the one-open-handle parse/revalidation boundary and the append-only delta path. For performance, batch multiple subagent lookups within one verified observation rather than weakening the rewrite witness. prefix_digest_bytes and parsed bytes must remain distinct: zero parsed records is not proof of zero read work. A parent-dispatch model is derived ingest metadata and must not override the provider/session/stream cursor authority in #948.
Returned CI is pending and profile/coverage queued. This finding takes precedence over treating the PR as correctness-complete pending only benchmarks.
Use Windows FILE_BASIC_INFO ChangeTime with last-write time to retain zero-byte exact hits while detecting exact-mtime rewrites. Revalidate a changed scan revision with one consumed-prefix digest and refuse mutation during validation; tests cover append, rewrite, replacement, and bounded multi-agent work.
…plan-reopened' into cursor/fix-cursor-parent-cache-f604
Keep the latest base identity-denial tests and the parent-cache ChangeTime rewrite test in one Windows-only module after merging #707.
…plan-reopened' into cursor/fix-cursor-parent-cache-f604
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 31c3dde0e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…plan-reopened' into cursor/fix-cursor-parent-cache-f604
…plan-reopened' into cursor/fix-cursor-parent-cache-f604
|
Accepted-direction HOLD is resolved on pushed head Correctness and work bounds:
Measured workload (
Verification:
The hosted MSVC Windows build/shards are queued behind the repository-wide CI concurrency holder; this comment does not claim that pending runtime result. PR is ready for review and remains unmerged for the parent. |
…sor/fix-cursor-parent-cache-f604
|
Final ancestry update: shared integration fast-forwarded this PR to Rechecked on
The current-head CI run is https://github.com/ScriptedAlchemy/tracedecay/actions/runs/34094861105. It is queued for hosted runners; the superseded prior-head run was cancelled so it cannot hold this one behind stale evidence. |
Performance Comparison
|
The prefix digest only covers [0, verified_cursor). A model parsed from the unterminated tail beyond it was still returned when the native revision moved during the scan, so a rewritten tail could attach a stale model to newly ingested subagent messages. Drop the transient model whenever the final revision differs from the scanned one; the next lookup re-reads the tail. Adds a test that rewrites the partial tail between scan and commit.
…sor/fix-cursor-parent-cache-f604
9b35c38
into
codex/tracedecay-total-redesign-plan-reopened
Summary
Motivation
Fixes #928. Cursor parent-dispatch caches can retain stale models when content is rewritten at the same length and timestamp, or when a file is replaced at the same path.
Changes
Test plan
cargo test -p tracedecay-sessions parent_dispatch_index::tests -- --test-threads=1(17/17; repeated)cargo clippy -p tracedecay-sessions --all-targets -- -D warningscargo check -p tracedecay-sessions --target x86_64-pc-windows-gnucargo bench -p tracedecay-sessions --bench cursor_dispatch_modelcargo test -p tracedecay-sessions(655 passed; five environment-sensitive failures outside the parent-dispatch cache: three existing JSONL same-handle timestamp tests and two Codex frontier tests)Checklist
CHANGELOG.mdupdated (not required for this scoped regression fix).envfiles included