Found while writing tests that exercise the repo-contract legacy fallback for the first time (PR forthcoming). Not a bug in existing code — a gap in what the migration mechanism covers.
The gap
legacy_contracts.toml / LEGACY_REPO_CONTRACT_WASM_HASHES covers the repo contract only. There is no equivalent for the pack contract.
Object bundles live in pack contracts keyed by BLAKE3(BLAKE3(pack_wasm) || pack_hash), and both fetch paths derive that key from the current pack_wasm with no fallback:
wsclient::get_pack (crates/freenet-git/src/wsclient.rs) — ContractKey::from_params_and_code(Parameters::from(pack_hash), ContractCode::from(pack_wasm))
chunked::fetch_chunked_pack_with_progress — same derivation per chunk
So if contracts/pack-contract.wasm is ever rebuilt, every previously-published bundle becomes unreachable, and unlike the repo contract there is no mechanism to find it.
Why this is reachable
Both WASM artifacts are checked in and both date from 2026-04-30. A rebuild is triggered by a freenet-stdlib bump, a toolchain bump, or any change to either contract crate — and a stdlib/toolchain bump rebuilds both contracts, not just the one whose source changed.
#63 documents the re-key procedure for the repo contract (capture the old BLAKE3, add a legacy_contracts.toml entry, rebuild). Following it exactly still leaves the pack side unhandled.
Symptom if it happens
Worse than a clean failure, because the repo half succeeds:
git clone falls back, finds the repo state at the legacy repo key, migrates it forward.
- Refs and
object_index list normally.
- Every bundle download fails — the packs are at pack-contract keys nobody computes any more.
Existing clones are unaffected (PackCache is keyed by content hash, not contract key) and new pushes self-heal at the new key, so the loss is specifically historical objects for anyone who has not already cloned.
Possible directions (not a proposal)
- A
legacy_pack_contracts registry plus fallback in get_pack / the chunked fetch path, symmetric with the repo side. Cheap, but re-PUT-forward is expensive: it means republishing every bundle in the repo.
- Or: treat a pack-contract re-key as requiring a
freenet-git rescue pass that re-PUTs bundles at the new key, and note in the release procedure that this must happen before the old copies age out. Note rescue currently GETs via get_pack at the current key, so it cannot do this today either.
- Or: make the pack-contract WASM hash not part of the pack key at all, if the pack contract is a pure content-addressed blob store where the code identity adds nothing. Biggest change, removes the failure class.
Worth deciding before #63's rebuild, since that is the first re-key event on the horizon.
[AI-assisted - Claude]
Found while writing tests that exercise the repo-contract legacy fallback for the first time (PR forthcoming). Not a bug in existing code — a gap in what the migration mechanism covers.
The gap
legacy_contracts.toml/LEGACY_REPO_CONTRACT_WASM_HASHEScovers the repo contract only. There is no equivalent for the pack contract.Object bundles live in pack contracts keyed by
BLAKE3(BLAKE3(pack_wasm) || pack_hash), and both fetch paths derive that key from the currentpack_wasmwith no fallback:wsclient::get_pack(crates/freenet-git/src/wsclient.rs) —ContractKey::from_params_and_code(Parameters::from(pack_hash), ContractCode::from(pack_wasm))chunked::fetch_chunked_pack_with_progress— same derivation per chunkSo if
contracts/pack-contract.wasmis ever rebuilt, every previously-published bundle becomes unreachable, and unlike the repo contract there is no mechanism to find it.Why this is reachable
Both WASM artifacts are checked in and both date from 2026-04-30. A rebuild is triggered by a freenet-stdlib bump, a toolchain bump, or any change to either contract crate — and a stdlib/toolchain bump rebuilds both contracts, not just the one whose source changed.
#63 documents the re-key procedure for the repo contract (capture the old BLAKE3, add a
legacy_contracts.tomlentry, rebuild). Following it exactly still leaves the pack side unhandled.Symptom if it happens
Worse than a clean failure, because the repo half succeeds:
git clonefalls back, finds the repo state at the legacy repo key, migrates it forward.object_indexlist normally.Existing clones are unaffected (
PackCacheis keyed by content hash, not contract key) and new pushes self-heal at the new key, so the loss is specifically historical objects for anyone who has not already cloned.Possible directions (not a proposal)
legacy_pack_contractsregistry plus fallback inget_pack/ the chunked fetch path, symmetric with the repo side. Cheap, but re-PUT-forward is expensive: it means republishing every bundle in the repo.freenet-git rescuepass that re-PUTs bundles at the new key, and note in the release procedure that this must happen before the old copies age out. Noterescuecurrently GETs viaget_packat the current key, so it cannot do this today either.Worth deciding before #63's rebuild, since that is the first re-key event on the horizon.
[AI-assisted - Claude]