Skip to content

fix: make bundle-tip extensions propagate (write them at update_seq 1) - #62

Merged
sanity merged 1 commit into
mainfrom
fix/bundle-tip-seq
Aug 4, 2026
Merged

fix: make bundle-tip extensions propagate (write them at update_seq 1)#62
sanity merged 1 commit into
mainfrom
fix/bundle-tip-seq

Conversation

@sanity

@sanity sanity commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Follow-up to the loose end noted after #61: a repo's extensions map was arriving empty on peers, while its refs and object_index propagated normally. That silently disables the dead-weight bundle filter (#32), so fetches download every bundle in object_index rather than the reachable ones — worst for snapshot-mode mirrors, where most bundles are dead weight by design.

Cause

In the contract's delta computation:

summary.extension_seqs.get(k).copied().unwrap_or(0) < v.update_seq

unwrap_or(0) reads a key the peer has never seen as seq 0, which is indistinguishable from a key it already holds at seq 0.

bundle-tip:<id> entries are written at seq 0 deliberately — each key is unique per bundle, so there is no monotonicity to track. So the test is 0 < 0, the entry is never included in a delta, and it never reaches that peer. Refs escape only because the push path always writes update_seq >= 1, which is a property of a different crate that nothing in get_state_delta enforces.

Two changes, because the obvious fix cannot reach the network

1. get_state_delta distinguishes absent from zero (extensions and refs). This is the root fix — but it lives in the contract WASM, and contracts/repo-contract.wasm is a checked-in artifact last built in April. Rebuilding it re-keys every repo and needs a legacy_contracts.toml migration entry, so it is a deliberate operation, not a side effect of this PR. This fix therefore takes effect on the next contract bump, not now. Filed as #63.

2. The push path writes tips at seq 1 (BUNDLE_TIP_UPDATE_SEQ). Under the deployed rule 0 < 1 holds, so tips propagate today. It remains correct after a contract rebuild: a peer missing the key still needs it, one holding it at seq 1 still does not.

Verification — controlled A/B on the live network

Two repos pushed minutes apart, differing only in this seq, then read from a third, fresh node that had to obtain both from the network:

repo tip update_seq result on the third node
ext-probe (control) 0 extensions (0)lost
seqfix-probe (fixed) 1 extensions (2)propagated

refs and object_index intact in both, reproducing the original symptom exactly.

User-visible effect, cloning each from that node:

### control (seq 0): filter disengaged, everything is "legacy"
    [legacy bundle 1/2] downloading pack (185 B)
    [legacy bundle 2/2] downloading pack (220 B)

### fixed (seq 1): filter engaged, tipped path
    [bundle 1] downloading pack (219 B)
    [bundle 2] downloading pack (186 B)

Note on the seq test

bundle_tip_seq_survives_the_deployed_contracts_delta_rule deliberately replicates the deployed rule instead of calling get_state_delta. That function is fixed in source now, so calling it would test the future and quietly stop testing what is actually running on the network. The test says so, and says to delete the replica when the contract is rebuilt — not before.

Scope note

Existing seq-0 tips already written stay unpropagatable; new pushes write seq 1 and propagate, so repos heal as they are pushed. No regression either way — those bundles are treated as legacy and downloaded, which is exactly what happens today.

[AI-assisted - Claude]

A repo's `extensions` map arrived empty on any peer that synced by
summary+delta, while its refs and object_index propagated normally. That
silently disables the dead-weight bundle filter (#32), so a fetch
downloads every bundle in object_index instead of just the reachable
ones -- worst for snapshot-mode mirrors, where most bundles are dead
weight by design.

Cause is in the contract's delta computation:

    summary.extension_seqs.get(k).copied().unwrap_or(0) < v.update_seq

`unwrap_or(0)` reads a key the peer has NEVER SEEN as seq 0, which is
indistinguishable from a key it already holds at seq 0. `bundle-tip:<id>`
entries are written at seq 0 on purpose -- each key is unique per
bundle, so there is no monotonicity to track -- so the test is `0 < 0`
and the entry is never put in a delta and never reaches that peer.
Refs escape only because the push path always writes seq >= 1.

Two changes, because the obvious fix cannot reach the network:

1. `get_state_delta` now distinguishes absent from zero, for both
   extensions and refs. This is the root fix, but it lives in the
   contract WASM, and `contracts/repo-contract.wasm` is a checked-in
   artifact only rebuilt deliberately -- a rebuild re-keys every repo
   and needs a legacy_contracts.toml migration entry. So it takes
   effect on the next contract bump, not now. Filed separately.

2. The push path writes bundle-tip extensions at seq 1
   (`BUNDLE_TIP_UPDATE_SEQ`). Under the deployed rule `0 < 1` is true,
   so the entry propagates today. It stays correct after a contract
   rebuild: a peer missing the key still needs it, one holding it at 1
   still does not.

Verified as a controlled A/B on the live network. Two repos pushed
minutes apart, differing only in this seq, then read from a third node
that had to obtain both from the network:

    ext-probe    (seq 0)  ->  extensions (0)   <- lost
    seqfix-probe (seq 1)  ->  extensions (2)   <- propagated

with refs and object_index intact in both, reproducing the original
symptom exactly. Cloning the control reports every bundle as "legacy"
(filter disengaged); cloning the fixed repo takes the tipped path.

The unit test for the seq deliberately replicates the DEPLOYED delta
rule rather than calling `get_state_delta`, which has now been fixed in
source -- calling it would test the future and stop testing what is
actually running.

[AI-assisted - Claude]
@sanity
sanity merged commit e2278ff into main Aug 4, 2026
5 checks passed
sanity added a commit that referenced this pull request Aug 4, 2026
Ships the #62 bundle-tip propagation fix. Tips written at update_seq 0
were undeliverable under the deployed contract's delta rule, so a repo's
extensions map arrived empty on peers that synced by summary+delta,
silently disabling the dead-weight bundle filter and making fetches
download every bundle in object_index.

The fix is in the push path, so it only takes effect for pushes made by
a published binary -- hence the release. Repos heal as they are pushed;
previously-written seq-0 tips stay unpropagatable, which is the status
quo (those bundles are treated as legacy and downloaded).

The root fix in `get_state_delta` also landed in #62 but is inert until
the contract WASM is rebuilt, which re-keys every repo. Tracked in #63.

Only the freenet-git crate changed; the sibling crates are unchanged
and stay at their published versions.

[AI-assisted - Claude]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant