import: a .rsbank lands as a new bank, whole or not at all
Four collisions answered explicitly: ids reminted, names never overwritten, content deduped before the write, bank name auto-suffixed. Degraded ledger refuses before the picker.
This commit is contained in:
@@ -73,6 +73,11 @@ landing after the format.
|
||||
here, computed where payloads are streamed (shell). The bank's `slot_map`
|
||||
rides along. Unknown keys skip at every level; duplicate entry names are
|
||||
rejected both ways.
|
||||
- `import_plan` — the pure import decision, and the reason the whole feature is
|
||||
testable without a DAW: the destination bank's display name after
|
||||
`BankBook`'s own fold, the reminted sample ids and remapped parents, and the
|
||||
per-entry land / collapse / rename disposition. Also `importLedgerRefusal`,
|
||||
the import's ledger gate.
|
||||
- `bank_package` — framing and arithmetic composing the two above:
|
||||
`encodePackage` (prefix bytes + layout + total size, stamping this build's
|
||||
ladder pair and `version::stampVersion()`), `decodePackage` (prefix + observed
|
||||
@@ -91,6 +96,11 @@ landing after the format.
|
||||
deliberately NOT enforced by the codec — they are `import_plan` decisions. The
|
||||
codec rejects only what makes the container itself incoherent (duplicate
|
||||
entry names, invalid names, a non-single-sample nested index).
|
||||
- **`import_plan` consults no other bank's hashes, and that is the ruling, not
|
||||
an omission.** An import always creates a NEW bank, so "already present in the
|
||||
destination bank by content" is exactly "already landed by this same plan".
|
||||
Cross-bank dedup is not enforced anywhere (`core/model/CLAUDE.md`), so a hash
|
||||
the pool already holds still lands its own file here.
|
||||
- `requiredPrefixSize` trusts fields beyond the frozen region only when the
|
||||
version pair classifies `Readable`; for `TooNew` it stops at the semver —
|
||||
don't "fix" it to read the manifest length there, a future structural format
|
||||
@@ -133,15 +143,13 @@ landing after the format.
|
||||
collision class as the ASCII case fold, which `sameEntryName` does catch. A
|
||||
table-free fix does not exist, and restricting names to ASCII would be
|
||||
genuinely over-strict for non-English users. Left open knowingly.
|
||||
- **`duplicateName` is O(n²) over `entries` on the decode path** — pre-existing
|
||||
shape (the double loop is unchanged since `af35fc5`; only the comparator
|
||||
changed). Under the `kMaxManifestBytes` cap (64 MB) a minimal entry is
|
||||
~100 bytes, so a hostile package can declare ~670k entries — ~2×10¹¹ pair
|
||||
comparisons, a multi-minute hang on import. It signals an error rather than
|
||||
UB, so the hostile-input invariant above still holds, but it sits against
|
||||
this module's "a forged header cannot demand gigabytes" posture. Forward
|
||||
obligation for `import_plan`: fold this into a sorted vector or hash set
|
||||
when that track lands; not changed here.
|
||||
- **`duplicateName` folds through a hash set, not a pairwise scan.** Under the
|
||||
`kMaxManifestBytes` cap (64 MB) a minimal entry is ~100 bytes, so a hostile
|
||||
package can declare ~670k entries; the former double loop was ~2×10¹¹ pair
|
||||
comparisons — a multi-minute hang on the decode path an import drives. The
|
||||
set is keyed on `entryNameKey`, which is `sameEntryName`'s ASCII-case fold
|
||||
made explicit, so the equivalence rule still has one home (`lowerAscii`).
|
||||
Do not reintroduce the pairwise scan.
|
||||
- **Cross-module contract with `src/shell/package`:** a genuinely zero-length
|
||||
entry cannot round-trip through the filesystem seam there (`appendPayload`
|
||||
refuses an empty payload — an empty buffer signals an upstream read failure,
|
||||
|
||||
Reference in New Issue
Block a user