feat(routing): Phase-2 size-routing primitives (slice 1, behavior-neutral) #18
+549
−1
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Phase 2, Slice 1 — size-routing primitives (behavior-neutral)
First slice of Phase 2 (size-routing / append) per the RFC in #7 (§2, §2.10). Pure,
additive helpers — nothing wires them into the archive or reclaim flow yet, so this is
behavior-neutral. Full suite 136 → 172 green. Safe to land ahead of the full RFC
review (Jarrod OK'd proceeding with slice 1).
What lands (all in
archive.py, reusing the merged PR #8 primitives)route(catalog, t_small, shard_count)→[{slot, kind, arcnames}]— partition:files ≥
T_smallbecome solo objects; smaller files bucket into content-addressedshards
k = shake_128(arcname) mod K. Deterministic order; empty buckets omitted;disjoint union of the catalog.
shard_of/solo_slot/shard_slot/derive_shard_count—K = ceil(small_bytes / shard_target), meant to be frozen per badge at baseline (a content-addressed mapmust be stable).
object_fingerprint— solo{mtime_s, size}; shard{members, byte_sum, digest}where
digest=shake_128over sorted(arcname, floor_s(mtime), size)triples (theauthoritative change signal; catches add/remove/modify/size and a same-count/
same-bytes reshuffle).
object_unchanged— the skip/append signal (also the resumemechanism in slice 2).
clamp_t_small(100 MB floor),budget_warnings(warn-only:max_solo,max_objects_per_target, oversize shard),vault_dir_for/manifest_path/load_manifest/write_manifest(atomictmp+os.replace, best-effort; the_vault/child dodgesreclaim.find_logs' non-recursive glob).make_zip_filesgains an optionalonly_arcnamesfilter (no caller yet) so a laterslice can ship exactly one routed object. Keys stay
os.path.relpatharcnames(invariant Canonical-root invariant: paths reclaim can never delete #2); an empty object still raises the clear error.
Tests —
tests/test_routing_primitives.py(36 cases)Boundary partition (≥ T_small = solo), shard stability (same arcname → same k), K derived
object_unchangedskip vsshard-append,
only_arcnamesnarrows to the subset with matching checksum keys, manifestround-trip + no leftover
.tmp+_vault/placement. Existing 136 unchanged.python3 -m unittest discover -s tests→Ran 172 tests … OK.Not in this slice (gated on the #7 review)
Archive wiring (
route_and_ship, config keys,--globusrefusal,--fresh), aggregatereclaim (
verify_target), reship-monitor, andrestore.py— slices 2–5.🤖 Generated with Claude Code