gabriel / musehub public
fix BREAKING fix/push-empty-manifest-delta-parent #1 / 1
AI Agent gabriel · 1 day ago · Jun 16, 2026 · Diff

fix: recover from corrupt snapshot manifest_blob on read

Staging gabriel/muse clone failed because 4 snapshots were stored by a legacy push version (2026-05-27/28) with an empty manifest_blob that no longer reproduces its snapshot_id. The serving path trusted the corrupt cache, the client's hash check failed, the commit was dropped, and every descendant failed 'parent not in mpack' — empty working tree.

Harden the serving path to be self-healing (repair-on-read): - _cached_manifest_reproduces(): validate a cached manifest against its snapshot_id (stored/derived/empty dirs). - _reconstruct_manifest_validated(): walk the parent chain, validate each cached manifest, SKIP corrupt caches (treat as delta-only), apply deltas forward from the nearest intact ancestor. Unlike _reconstruct_manifest, a corrupt blob no longer terminates the walk. - _snap_row_to_wire_s3(): validate the fast-path cache; reconstruct via the validated walker when the cache is absent OR corrupt.

Current push code was proven correct (does NOT reproduce the corruption) — this is a legacy-data defense. Also fixes a stale comment in wire_push_unpack_mpack that claimed delta-only snapshots are 'backfilled by the mpack.index job'; they are not — they are reconstructed on-demand from the delta chain.

Tests (integration, localhost): serve-corrupt recovery (RED->GREEN) + push guard for delta-only external parents. 15/15 existing fetch/serving tests still pass.

sha256:c00aa21d4054a2ab58d31aa5a1a1c4ca8d4a6bdc8a0bf03f7523b545889ae58f sha
+49 ~1 symbols
sha256:5ced872171974351ec7df1f684932eb80cf6e7b6222589ddd8fae4f01e84875c snapshot
+49
symbols added
~1
symbol modified
0
dead code introduced
Semantic Changes 50 symbols
+ DB_URL variable variable DB_URL L44–44
+ HUB variable variable HUB L43–43
+ REPO_ROOT variable variable REPO_ROOT L45–45
+ _commit_id_by_message function function _commit_id_by_message L61–68
+ _stored_snapshot_for_commit function async_function _stored_snapshot_for_commit L71–90
+ hub_repo function function hub_repo L94–104
+ AsyncSession import import AsyncSession L37–37
+ MusehubCommit import import MusehubCommit L41–41
+ MusehubSnapshot import import MusehubSnapshot L41–41
+ Path import import Path L33–33
+ annotations import import annotations L28–28
+ asyncio import import asyncio L30–30
+ create_async_engine import import create_async_engine L37–37
+ hash_snapshot import import hash_snapshot L40–40
+ json import import json L31–31
+ msgpack import import msgpack L35–35
+ pytest import import pytest L36–36
+ sessionmaker import import sessionmaker L38–38
+ subprocess import import subprocess L32–32
+ muse function function muse L48–51
+ muse_check function function muse_check L54–58
+ test_child_of_delta_only_parent_keeps_complete_manifest function function test_child_of_delta_only_parent_keeps_complete_manifest L107–153
+ DB_URL variable variable DB_URL L42–42
+ HUB variable variable HUB L41–41
+ REPO_ROOT variable variable REPO_ROOT L43–43
+ _commit_id_by_message function function _commit_id_by_message L59–64
+ _corrupt_then_serve function async_function _corrupt_then_serve L79–117
+ _snapshot_id_for_commit function async_function _snapshot_id_for_commit L67–76
+ hub_repo function function hub_repo L121–130
+ AsyncSession import import AsyncSession L34–34
+ MusehubCommit import import MusehubCommit L38–38
+ MusehubSnapshot import import MusehubSnapshot L38–38
+ Path import import Path L30–30
+ _snap_row_to_wire_s3 import import _snap_row_to_wire_s3 L39–39
+ _time import import _time L29–29
+ annotations import import annotations L24–24
+ asyncio import import asyncio L26–26
+ create_async_engine import import create_async_engine L34–34
+ hash_snapshot import import hash_snapshot L37–37
+ json import import json L27–27
+ msgpack import import msgpack L32–32
+ pytest import import pytest L33–33
+ sessionmaker import import sessionmaker L35–35
+ subprocess import import subprocess L28–28
+ muse function function muse L46–49
+ muse_check function function muse_check L52–56
+ test_serving_path_recovers_from_corrupt_manifest_blob function function test_serving_path_recovers_from_corrupt_manifest_blob L133–165
~ musehub/services/musehub_wire_shared.py .py 2 symbols added, 1 symbol modified
+ _cached_manifest_reproduces function function _cached_manifest_reproduces L156–165
+ _reconstruct_manifest_validated function async_function _reconstruct_manifest_validated L168–213
← Older Oldest on fix/push-empty-manifest-delta-parent
All commits
Newer → Latest on fix/push-empty-manifest-delta-parent

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:c00aa21d4054a2ab58d31aa5a1a1c4ca8d4a6bdc8a0bf03f7523b545889ae58f --body "your comment"