Skip to content

Commit 6e5071d

Browse files
solomonneascodex
andcommitted
fix(receipts): preserve sidecar evidence
Co-Authored-By: Codex <codex@openai.com>
1 parent df0db63 commit 6e5071d

6 files changed

Lines changed: 363 additions & 8 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Memory Handoff
2+
3+
## Type
4+
5+
bugfix
6+
7+
## Title
8+
9+
Write-once run sidecar revisions
10+
11+
## Summary
12+
13+
Resume and patch-reference updates previously replaced worker and synthesis sidecars in place. They now append private, immutable revisions while retaining the legacy files as current compatibility projections.
14+
15+
## Durable facts
16+
17+
- Initial emissions and every update append six-digit JSON revisions below `revisions/worker-results/` and `revisions/synthesis/`.
18+
- A first update of a legacy run imports the compatibility projection as revision 000001 before appending the new revision.
19+
- A failed compatibility projection write must not replace the old projection after immutable evidence has been written.
20+
- Revision files use mode 0600. POSIX writes fsync each new directory entry, the revision file, and the leaf directory before updating the projection.
21+
- Existing readers continue loading `worker-results.json` and `synthesis.json`; no receipt schema or dependency changed.
22+
23+
## Evidence
24+
25+
- files changed: `src/brigade/aboyeur.py`, `src/brigade/run_resume.py`, `tests/test_aboyeur.py`, `tests/test_run_resume.py`, `tests/test_runs_cmd.py`
26+
- RED receipts: `20260728-030303-work-verify-9ae890`, `20260728-031100-work-verify-052751`, `20260728-031433-work-verify-ca2331`, `20260728-031552-work-verify-cf2e04`
27+
- GREEN receipts: `20260728-031646-work-verify-d12faa`, `20260728-031656-work-verify-8bfb8d`, `20260728-031822-work-verify-4cb89a`, `20260728-031829-work-verify-d68b11`
28+
29+
## Recommended memory action
30+
31+
no-card
32+
33+
## Target document
34+
35+
.learnings/LEARNINGS.md
36+
37+
## Suggested document content
38+
39+
### Write-once run sidecar revisions
40+
41+
Worker and synthesis evidence is append-only under `revisions/<sidecar>/`, while the original sidecar paths remain current compatibility projections. On the first update of a legacy run, archive the exact old bytes before appending the updated document. Persist revision evidence before replacing the projection, and retain the new revision if projection replacement fails. On POSIX, directory-entry fsync ordering is part of the durability contract.

src/brigade/aboyeur.py

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,92 @@ def _write_json(path: Path, payload: object) -> None:
492492
localio.write_text_atomic(path, json.dumps(payload, indent=2, sort_keys=True) + "\n")
493493

494494

495+
def _revision_contains(revisions_dir: Path, projection: bytes) -> bool:
496+
"""Return whether a preserved sidecar revision matches ``projection``."""
497+
if not revisions_dir.is_dir():
498+
return False
499+
for revision_path in revisions_dir.glob("*.json"):
500+
try:
501+
if revision_path.read_bytes() == projection:
502+
return True
503+
except OSError:
504+
continue
505+
return False
506+
507+
508+
def _supports_directory_fsync() -> bool:
509+
return os.name == "posix"
510+
511+
512+
def _fsync_directory(path: Path) -> None:
513+
"""Persist directory entries where the platform supports directory fsync."""
514+
if not _supports_directory_fsync():
515+
return
516+
directory_fd = os.open(path, os.O_RDONLY)
517+
try:
518+
os.fsync(directory_fd)
519+
finally:
520+
os.close(directory_fd)
521+
522+
523+
def _ensure_directory_durable(path: Path) -> None:
524+
"""Create missing directory levels and persist each new parent entry."""
525+
missing: list[Path] = []
526+
current = path
527+
while not current.exists():
528+
missing.append(current)
529+
current = current.parent
530+
for directory in reversed(missing):
531+
try:
532+
directory.mkdir()
533+
except FileExistsError:
534+
if not directory.is_dir():
535+
raise
536+
else:
537+
_fsync_directory(directory.parent)
538+
539+
540+
def _write_new_revision(revisions_dir: Path, encoded: bytes) -> None:
541+
"""Exclusively create the next immutable sidecar revision."""
542+
_ensure_directory_durable(revisions_dir)
543+
sequence = max(
544+
(int(path.stem) for path in revisions_dir.glob("*.json") if path.stem.isascii() and path.stem.isdecimal()),
545+
default=0,
546+
)
547+
while True:
548+
sequence += 1
549+
revision_path = revisions_dir / f"{sequence:06d}.json"
550+
writer_acquired = False
551+
try:
552+
fd = os.open(revision_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
553+
writer_acquired = True
554+
with os.fdopen(fd, "wb") as handle:
555+
handle.write(encoded)
556+
handle.flush()
557+
os.fsync(handle.fileno())
558+
_fsync_directory(revisions_dir)
559+
except FileExistsError:
560+
continue
561+
except BaseException:
562+
if writer_acquired:
563+
revision_path.unlink(missing_ok=True)
564+
raise
565+
return
566+
567+
568+
def write_sidecar_revision(run_dir: Path, filename: str, payload: object) -> None:
569+
"""Append an immutable sidecar revision, then update its compatibility file."""
570+
projection_path = run_dir / filename
571+
revisions_dir = run_dir / "revisions" / Path(filename).stem
572+
if projection_path.exists():
573+
legacy_projection = projection_path.read_bytes()
574+
json.loads(legacy_projection)
575+
if not _revision_contains(revisions_dir, legacy_projection):
576+
_write_new_revision(revisions_dir, legacy_projection)
577+
_write_new_revision(revisions_dir, (json.dumps(payload, indent=2, sort_keys=True) + "\n").encode("utf-8"))
578+
_write_json(projection_path, payload)
579+
580+
495581
def _utc_iso(value: datetime) -> str:
496582
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
497583

@@ -1759,7 +1845,7 @@ def set_artifact_patch_ref(output_dir: Path, patch_ref: str = "changes.patch") -
17591845
else:
17601846
receipt_schema.stamp_synthesis_document(payload)
17611847
try:
1762-
_write_json(path, payload)
1848+
write_sidecar_revision(output_dir, filename, payload)
17631849
except OSError as exc:
17641850
raise runguard.RunGuardError(f"failed to record artifact patch reference in {filename}: {exc}") from exc
17651851

@@ -2983,8 +3069,9 @@ def dispatch_interrupted() -> None:
29833069
worker_results = _mark_noop_worker_results(worker_results, suspected_noop)
29843070
if output_dir is not None:
29853071
worker_results = _write_worker_logs(output_dir, worker_results)
2986-
_write_json(
2987-
output_dir / "worker-results.json",
3072+
write_sidecar_revision(
3073+
output_dir,
3074+
"worker-results.json",
29883075
receipt_schema.worker_results_document(
29893076
_worker_payload(worker_results),
29903077
ground_truth=ground_truth,
@@ -3079,7 +3166,7 @@ def dispatch_interrupted() -> None:
30793166
ground_truth=ground_truth,
30803167
)
30813168
)
3082-
_write_json(output_dir / "synthesis.json", synthesis_payload)
3169+
write_sidecar_revision(output_dir, "synthesis.json", synthesis_payload)
30833170
if not final.ok:
30843171
if output_dir is not None:
30853172
finished_at = datetime.now(timezone.utc)

src/brigade/run_resume.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ def _resume_locked(run_dir: Path) -> int:
199199
for r in results
200200
]
201201
ground_truth = worker_data.get("ground_truth") or {}
202-
aboyeur._write_json(
203-
run_dir / "worker-results.json",
202+
aboyeur.write_sidecar_revision(
203+
run_dir,
204+
"worker-results.json",
204205
receipt_schema.worker_results_document(
205206
aboyeur._worker_payload(worker_results),
206207
ground_truth=ground_truth,
@@ -226,8 +227,9 @@ def _resume_locked(run_dir: Path) -> int:
226227
reasoning=orchestrator.reasoning,
227228
env=dict(orchestrator.env) if orchestrator.env is not None else None,
228229
)
229-
aboyeur._write_json(
230-
run_dir / "synthesis.json",
230+
aboyeur.write_sidecar_revision(
231+
run_dir,
232+
"synthesis.json",
231233
receipt_schema.synthesis_document(
232234
orchestrator=roster.orchestrator,
233235
result={"ok": final.ok, "detail": final.detail, "text": final.text},

0 commit comments

Comments
 (0)