Relabel post-manifest-parse failure as TooNew; refuse zero-length package entries at encode
An additively-tagged newer package that fails to parse now reports TooNew (with writer semver) instead of unactionable Malformed. Format layer also refuses encoding a zero-length entry, honoring the shell's appendPayload contract; both test-covered.
This commit is contained in:
@@ -79,8 +79,22 @@ landing after the format.
|
||||
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
|
||||
may have moved it.
|
||||
- A package whose header classifies `Readable` (fv > ours, minReader still
|
||||
within reach — the additive case) but whose manifest fails to parse is
|
||||
reported `TooNew`, not `Malformed`: the header is valid and already carries
|
||||
the writer's semver, so the refusal can still name what to install. This
|
||||
widens `TooNew` to cover "read and failed" as well as "stopped at the frozen
|
||||
region" — both refuse whole and write nothing, so the safety property is
|
||||
unchanged, only the message. `classifyPackageVersion` and the frozen-region
|
||||
`TooNew` path are unaffected; this is the post-manifest-parse branch only.
|
||||
- The format carries no algorithm tag for `byteHash` — it is FNV-1a
|
||||
(`capture::hashBytes`) implicitly. Changing the digest algorithm is a
|
||||
`minReaderVersion` bump, not additive: an old reader would otherwise compare
|
||||
a stored digest against bytes hashed the new way and silently misjudge
|
||||
corruption.
|
||||
- **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,
|
||||
not a real entry). `serializeManifest` refuses a zero-length `PackageEntry`
|
||||
at encode so this layer never produces one; decode does not enforce it (a
|
||||
hostile/older package declaring one is not this track's concern).
|
||||
|
||||
@@ -102,7 +102,13 @@ DecodedPackage decodePackage(const std::vector<std::uint8_t>& prefix,
|
||||
if (!r.ok) return dec;
|
||||
|
||||
auto manifest = deserializeManifest(manifestJson);
|
||||
if (!manifest) return dec;
|
||||
if (!manifest) {
|
||||
// A newer additive format's parse failure reports TooNew, not the
|
||||
// unactionable Malformed — the header (with the writer semver) is
|
||||
// already valid here. See this directory's CLAUDE.md for the tradeoff.
|
||||
if (formatVersion > kPackageFormatVersion) dec.status = PackageReadability::TooNew;
|
||||
return dec;
|
||||
}
|
||||
|
||||
std::uint64_t end = 0;
|
||||
std::vector<PackageEntrySpan> layout;
|
||||
|
||||
@@ -43,8 +43,12 @@ bool PackageManifest::operator==(const PackageManifest& o) const {
|
||||
}
|
||||
|
||||
std::optional<std::string> serializeManifest(const PackageManifest& m) {
|
||||
for (const auto& e : m.entries)
|
||||
for (const auto& e : m.entries) {
|
||||
if (!isValidEntryName(e.fileName)) return std::nullopt;
|
||||
// Cross-module contract with src/shell/package — see this directory's
|
||||
// CLAUDE.md.
|
||||
if (e.byteLength == 0) return std::nullopt;
|
||||
}
|
||||
if (duplicateName(m.entries)) return std::nullopt;
|
||||
|
||||
std::string out;
|
||||
|
||||
@@ -41,9 +41,10 @@ struct PackageManifest {
|
||||
};
|
||||
|
||||
// Emits the manifest JSON. nullopt when the manifest cannot be represented:
|
||||
// an invalid or duplicate entry name, or a sample record BankModel itself would
|
||||
// reject (empty id, absolute path) — refusing on encode so an undecodable
|
||||
// package is never written.
|
||||
// an invalid or duplicate entry name, a zero-length entry (see this
|
||||
// directory's CLAUDE.md — the shell's payload-append seam cannot round-trip
|
||||
// one), or a sample record BankModel itself would reject (empty id, absolute
|
||||
// path) — refusing on encode so an undecodable package is never written.
|
||||
std::optional<std::string> serializeManifest(const PackageManifest& m);
|
||||
|
||||
// Parses manifest JSON (nullopt on malformed input, never UB). Unknown keys are
|
||||
|
||||
Reference in New Issue
Block a user