feat(parse-diff): classify live-write skew as raced, not changed#805
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
150e7a0 to
6323060
Compare
roborev: Combined Review (
|
246b9ae to
5d77679
Compare
Squash the parse-diff raced-skew work into one branch commit before rebasing onto origin/main. The series adds race-aware classification for live source writes while tightening the guard so shared, virtual, or DB-backed sources keep failing closed instead of masking real parser drift. It also fixes Codex index-title refresh paths that interacted with the same effective-mtime basis. Included follow-ups: - feat(parse-diff): classify live-write skew as raced, not changed - fix(parse-diff): resolve live-write skew mtime per session - fix(parse-diff): only apply raced guard to reliable file-based sources - fix(parse-diff): source raced live mtime from the parsed effective File.Mtime - fix(parse-diff): re-stat source after read for the raced live mtime - fix(parse-diff): recompute effective live mtime at collect time - fix(parse-diff): skip raced guard for sources shared by many sessions - fix(sync): refresh Codex title when index mtime is below stored mtime - fix(sync): refresh Codex title in quick sync when index mtime is below stored mtime - test(parse-diff): isolate all agent env vars in fail-on-change tests - fix(parse-diff): ignore Codex index mtime for raced drift Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
Codex stores file_mtime on an index-folded basis so title changes invalidate normal sync caches. Parse-diff intentionally ignores the shared index as a raced signal, but that left a blind spot when the stored index mtime was newer than both transcript mtimes: a later transcript write could still compare below the stored value and be reported as real parser drift. Use the existing transcript fingerprint as the Codex fallback when the mtime comparison alone is inconclusive. That keeps index-only skew from masking unchanged transcript drift while still treating proven transcript rewrites as raced.
1e90b53 to
bd73851
Compare
The Codex raced-skew fallback treats file_hash as the transcript fingerprint. Incremental JSONL sync advanced file_size and file_mtime while preserving the previous full-parse hash, so an already-synced appended transcript could later look like a live rewrite and hide unchanged-source parser drift from --fail-on-change. Refresh the hash only for Codex incremental snapshots, where parse-diff depends on that fingerprint. Other incremental agents keep preserving the columns they do not recompute.
roborev: Combined Review (
|
Codex incremental sync stores file_size at the byte offset consumed by the incremental parser. Hashing the whole live file could pair that prefix size with a fingerprint from a later-grown transcript, which made parse-diff's Codex raced-skew fallback compare mismatched snapshots. Hash the same consumed prefix that file_size records so incremental metadata remains internally consistent even when a partial or concurrent append exists beyond the parser boundary.
NewDiffEngine returns the regular Engine type for report-only parse-diff work, so every public write-capable entrypoint has to enforce the forceParse boundary itself. SyncThenRun and RunExclusive were missed, leaving paths that could still drive sync work or caller-provided DB writes through a report-only engine. Guard both methods at entry so SyncThenRun behaves like the other no-op sync methods and RunExclusive fails before running caller work.
NewDiffEngine returns the normal Engine type for report-only parse-diff work, so every public DB-writing entrypoint has to enforce the forceParse boundary itself. RecomputeSignals and ScanSecrets were still able to rewrite quality signal and secret metadata through a parse-diff engine. Refuse both methods before recomputing or scanning so parse-diff callers cannot mutate stored session metadata through those public APIs.
roborev: Combined Review (
|
Codex incremental sync can store file_hash and file_size for the parsed prefix when a transcript has a partial trailing JSON line. Parse-diff compared that prefix snapshot with the full-file hash and size from a later full parse, so an unchanged ignored tail could look like a live transcript rewrite and hide real parser drift from --fail-on-change. Compare the stored prefix against the live prefix and only treat size skew as raced when the Codex parser would consume a different JSONL boundary. That keeps real consumed appends classified as races without letting ignored partial tails mask parser changes.
roborev: Combined Review (
|
Existing archives can contain Codex rows whose incremental metadata advanced file_size and file_mtime while file_hash still points at an older full-parse snapshot. Without a data-version bump or row marker, a hash-only mismatch is ambiguous and can hide real parser drift by classifying it as DiffRaced. Use the parser-consumed JSONL byte boundary as the raced fallback and fail closed on hash-only mismatches. That preserves append-race detection for consumed transcript changes while keeping legacy stale hashes visible to --fail-on-change.
roborev: Combined Review (
|
Parse-diff classifies races after the worker has already parsed the file, so the parser's File.Size can be stale by collection time. Codex's fallback used that stale size to return early, which left post-parse transcript appends classified as real parser drift. Recompute the live Codex consumed JSONL boundary whenever a stored file_size exists, and compare that collect-time boundary to the archived snapshot. This keeps the raced decision on the same post-read basis as the surrounding mtime guard.
Codex file_size has two historical bases: full parses record the raw file length, while incremental parses can record the consumed JSONL boundary when a partial line trails the file. Comparing only against the consumed boundary made stable full-parse archives with ignored partial tails look like live transcript changes. Check the live raw file size first and only fall back to the consumed boundary when the raw size no longer matches. That preserves collect-time append detection without masking parser drift on unchanged full-parse snapshots.
roborev: Combined Review (
|
|
phew! merging |
parse-diffre-parses on-disk sources and compares them against stored rows, so a parser change's effect can be gated in CI with--fail-on-change. Two things made that gate untrustworthy; this PR fixes both, plus a Codex title-refresh gap the work surfaced.Live-write skew classified as
racedparse-diffsnapshots stored rows, then re-parses sources; a daemon or an active session writing the archive mid-run produced torn comparisons reported aschanged, tripping--fail-on-changeon a write unrelated to the parser. After each per-file compare, the source mtime is re-stat'd (the same agent-aware nanosecond value sync stores infile_mtime) and compared against the snapshot'sSession.FileMtime; a file written after the snapshot is classifiedracedinstead ofchanged. Raced sessions are excluded from--fail-on-change, so a concurrent write no longer fails the run, while a genuine change on a demonstrably untouched file still does. Classification is conservative (unreadable source or missing stored mtime → raced) but never masks a real change.Report-only engine write guard
NewDiffEnginereturns the full*Engine, which still exposedSyncAll/ResyncAll, so a report-only/force-parse engine could in principle be driven into a write path.refuseWriteInForceParsenow guards those entrypoints as a no-op whenforceParseis set (onlyNewDiffEnginesets it). Additive; no existing write behavior changes.Codex title refresh (surfaced by this work)
To keep the raced guard consistent, the incremental write now folds the
session_index.jsonlmtime into the storedfile_mtime, exactly as a full parse does. That raised the stored watermark on the append path and exposed a latent gap: a title-only index rename whose mtime lands at or below the stored value was filtered out by anindexMtime > storedMtimegate (in both the incremental andSyncAllSincequick-sync paths), leaving the title stale behindshouldSkipCodex's fast path. Both paths now compare the index title to the storedsession_namedirectly. The broader "mtime-watermark conflates transcript + title" class is tracked as a follow-up.Scope
Covers the parse-diff v2 sub-items for live-write skew and the write-path guard, plus the Codex title-refresh fixes above. Incremental-append skew detection and DB-backed coverage for Warp/Forge/Piebald remain for a follow-up.
Reviewers:
internal/sync/parsediff.go,internal/sync/parsediff_compare.go,internal/sync/parsediff_report.go, and the Codex paths ininternal/sync/engine.go.