Skip to content

Commit a997c17

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

6 files changed

Lines changed: 359 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: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,88 @@ 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 _fsync_directory(path: Path) -> None:
509+
"""Persist directory entries where the platform supports directory fsync."""
510+
if os.name != "posix":
511+
return
512+
directory_fd = os.open(path, os.O_RDONLY)
513+
try:
514+
os.fsync(directory_fd)
515+
finally:
516+
os.close(directory_fd)
517+
518+
519+
def _ensure_directory_durable(path: Path) -> None:
520+
"""Create missing directory levels and persist each new parent entry."""
521+
missing: list[Path] = []
522+
current = path
523+
while not current.exists():
524+
missing.append(current)
525+
current = current.parent
526+
for directory in reversed(missing):
527+
try:
528+
directory.mkdir()
529+
except FileExistsError:
530+
if not directory.is_dir():
531+
raise
532+
else:
533+
_fsync_directory(directory.parent)
534+
535+
536+
def _write_new_revision(revisions_dir: Path, encoded: bytes) -> None:
537+
"""Exclusively create the next immutable sidecar revision."""
538+
_ensure_directory_durable(revisions_dir)
539+
sequence = max(
540+
(int(path.stem) for path in revisions_dir.glob("*.json") if path.stem.isascii() and path.stem.isdecimal()),
541+
default=0,
542+
)
543+
while True:
544+
sequence += 1
545+
revision_path = revisions_dir / f"{sequence:06d}.json"
546+
writer_acquired = False
547+
try:
548+
fd = os.open(revision_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
549+
writer_acquired = True
550+
with os.fdopen(fd, "wb") as handle:
551+
handle.write(encoded)
552+
handle.flush()
553+
os.fsync(handle.fileno())
554+
_fsync_directory(revisions_dir)
555+
except FileExistsError:
556+
continue
557+
except BaseException:
558+
if writer_acquired:
559+
revision_path.unlink(missing_ok=True)
560+
raise
561+
return
562+
563+
564+
def write_sidecar_revision(run_dir: Path, filename: str, payload: object) -> None:
565+
"""Append an immutable sidecar revision, then update its compatibility file."""
566+
projection_path = run_dir / filename
567+
revisions_dir = run_dir / "revisions" / Path(filename).stem
568+
if projection_path.exists():
569+
legacy_projection = projection_path.read_bytes()
570+
json.loads(legacy_projection)
571+
if not _revision_contains(revisions_dir, legacy_projection):
572+
_write_new_revision(revisions_dir, legacy_projection)
573+
_write_new_revision(revisions_dir, (json.dumps(payload, indent=2, sort_keys=True) + "\n").encode("utf-8"))
574+
_write_json(projection_path, payload)
575+
576+
495577
def _utc_iso(value: datetime) -> str:
496578
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
497579

@@ -1759,7 +1841,7 @@ def set_artifact_patch_ref(output_dir: Path, patch_ref: str = "changes.patch") -
17591841
else:
17601842
receipt_schema.stamp_synthesis_document(payload)
17611843
try:
1762-
_write_json(path, payload)
1844+
write_sidecar_revision(output_dir, filename, payload)
17631845
except OSError as exc:
17641846
raise runguard.RunGuardError(f"failed to record artifact patch reference in {filename}: {exc}") from exc
17651847

@@ -2983,8 +3065,9 @@ def dispatch_interrupted() -> None:
29833065
worker_results = _mark_noop_worker_results(worker_results, suspected_noop)
29843066
if output_dir is not None:
29853067
worker_results = _write_worker_logs(output_dir, worker_results)
2986-
_write_json(
2987-
output_dir / "worker-results.json",
3068+
write_sidecar_revision(
3069+
output_dir,
3070+
"worker-results.json",
29883071
receipt_schema.worker_results_document(
29893072
_worker_payload(worker_results),
29903073
ground_truth=ground_truth,
@@ -3079,7 +3162,7 @@ def dispatch_interrupted() -> None:
30793162
ground_truth=ground_truth,
30803163
)
30813164
)
3082-
_write_json(output_dir / "synthesis.json", synthesis_payload)
3165+
write_sidecar_revision(output_dir, "synthesis.json", synthesis_payload)
30833166
if not final.ok:
30843167
if output_dir is not None:
30853168
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)