Skip to content

Name frame versions once, and read a frame from what the file says - #94

Merged
physics515 merged 2 commits into
xname/declare-validationfrom
xframe/frame-registry
Oct 21, 2026
Merged

physics515 merged 2 commits into
xname/declare-validationfrom
xframe/frame-registry

Conversation

@physics515

Copy link
Copy Markdown
Collaborator

Stacked on #93 (PAC-3) — base that branch for a reviewable diff, or merge downwards.

What this is

The 1.0 plan's X-FRAME row (docs/release/1.0-plan.md:142, ROADMAP W2): a frame registry, the v7
envelope, the read_frame*/describe_frame family, and the FrameFault classes. It absorbs FRE-4,
TAG-2 and ROB-8's fault_class naming.

Frame versions are now named in one place — weft_physical_type::frame_registry:
FRAME_SINGLE_BLOCK = 5, FRAME_PAGED = 6, FRAME_ENVELOPE = 7, FRAME_VERSION_MAX_KNOWN = 7 —
with the rule in the module doc that a number is never reused or renumbered once it has shipped.

The envelope

wrap_frame_envelope puts a header and a trailer around an existing frame and stores that frame
verbatim, its own magic, version, body and CRC intact:

WEFTSEG | u16 7 | u16 body_version | uvarint ext_count | { uvarint id, u8 critical, varint len, payload }… | frame | u32 crc32

16 bytes of overhead (12-byte header with no extensions, plus the trailer). That is a deliberate
constraint: weftdb/src/types/segment_store.rs:7022 promises that an unconfigured deployment's
bytes are byte-for-byte what previous versions wrote, and the existing byte-pinning tests
(weftseg.rs:4153-4175, 4321-4341, segment_store.rs:117,154,196,217,381,2863,4721,…,
weft-server/src/ingest.rs:602,835, which put the frame version on the HTTP surface) stay green
because a v5 or v6 body is untouched. A legacy reader pointed at an envelope still says
UnsupportedVersion { found: 7 } rather than misparsing it.

An extension is {id, critical, payload}. Unknown critical extension ⇒ the frame is refused:
the writer produced that body assuming the extension would be applied, so continuing would serve
data under a contract nobody honoured. Unknown optional extension ⇒ ignored.
KNOWN_EXTENSIONS is empty on purpose — the envelope ships with no extensions, and the numbers are
a permanent namespace, so inventing one before a feature needs it costs a future release its only
free slot.

Reading without knowing the layout

describe_frame (metadata, no decode; Ok for a version this build does not know, with
layout: None), frame_layout_of (a header-only routing probe), unwrap_frame_envelope, and
read_frame_segment / read_frame_paged_segment / read_frame_point / read_frame_points /
read_frame_range / read_frame_range_physical.

frame_layout_of rather than describe_frame for routing, on purpose: it reads magic + version
(and, for 7, the body_version) and nothing else. describe_frame would pay a second CRC pass on
every read and would replace the reader's error sentence with its own. The chosen reader still
verifies every byte of the frame.

Fault classes

frame_fault_of(&WeftSegError) -> FrameFault — Integrity or Undecodable { newer_format }, with
fault_class() giving the stable strings integrity / newer_format / undecodable (plan C5,
docs/release/1.0-plan.md:176). UnsupportedVersion is compared against
FRAME_VERSION_MAX_KNOWN, so a frame from a newer build is distinguished from one that is damaged;
CodecNotEnabled and the new UnknownCriticalExtension are undecodable but not
newer_format, because the file is not ahead of us, our features are off.

WeftSegError is now #[non_exhaustive] and gains UnknownCriticalExtension { id } and
EnvelopeBodyMismatch { declared, found }.

The store

SegmentStore routed decodes by descriptor.format_version == PAGED_SEGMENT_FORMAT_VERSION, i.e.
by what the index row claimed. Anything that was not 6 — including the unwrap_or(0) in
segment_index.rs:433 — went to the single-block decoder. It now asks the file: the 6 call sites
(:292, :1924, :3067, :3203, :3241, :4265) go through weftdb/src/types/frame_read.rs,
which probes the header, calls the matching read_frame_*, and returns failures as
weftdb::FrameFaultError (public, in the anyhow chain) under the sentence
decoding paged segment <path>: … / decoding segment <path>: … unchanged, so nothing that
matched those strings keeps working differently.

HTTP mapping, at weft-server's classify_typed: Undecodable { newer_format: true } ⇒
503 incompatible_store, every other frame fault ⇒ 500 frame_undecodable, with the class in
details.fault. Code::FrameUndecodable was added by #90 with a wire sentence and no
producer; this is the producer.

Not included

No writer opts into v7 — seals still write 5 and 6. This slice ships the mechanism and the classes;
the v7 opt-in and the C5 quarantine/Weft-Incomplete behaviour belong to D-S12/TAG-9 and the ingest
lanes. Frame naming ({enc(aspect)}~g{gen}~p{prec}.weftseg, the 200-byte stem cap and the
~h{hash} cut) is already on main in weftdb/src/types/frame_name.rs and is untouched here.

Testing

  • weft-physical-type: cargo +stable test -p weft-physical-type --lib — 306 passed, of which 14
    are new: envelope round-trip, a legacy reader refusing an envelope with UnsupportedVersion,
    critical-extension refusal, optional-extension ignore, count/header disagreement caught by CRC,
    describe_frame reporting a future version, frame_fault_of classification, and the
    layout-aware read family over a wrapped frame.
  • weftdb: cargo +stable test -p weftdb --lib frame_read — 6 passed (the crate's lib suite is 395).
    New tests in weftdb/src/types/frame_read.rs cover routing through an envelope, FrameFault::Integrity
    on a flipped byte, newer_format on a rewritten version (CRC re-stamped, so the checksum is not what
    complains), a forged newer-body envelope, critical-extension refusal through the store, and the
    decoding paged segment … sentence with the fault in the chain.
  • weft-server: cargo +stable test -p weft-server --test frame_fault — 2 passed. The C5 mapping is
    asserted end to end over a real sealed frame: a flipped byte in the middle of a .weftseg answers
    500 frame_undecodable with details.fault == "integrity", and a version byte pair rewritten past
    FRAME_VERSION_MAX_KNOWN with the trailer CRC re-stamped answers 503 incompatible_store with
    details.fault == "newer_format". The body carries the fixed X-ERR sentence, not the path or the
    decoder's complaint.
  • Store-level: the existing read tests exercise the rewired sites (they decode real frames written
    by persist/persist_paged).
  • Gates: cargo +stable check --workspace --all-targets --locked, cargo +stable clippy --workspace --all-features --all-targets, RUSTDOCFLAGS="-D warnings" cargo +stable doc --workspace --no-deps --locked, cargo +nightly fmt --all -- --check.

Review notes

  • read_bound_frame still checks bytes.len() == descriptor.byte_len and the bound frame_crc
    against the file's last four bytes. Under an envelope those bytes are the envelope's CRC, which
    is the right thing to compare only while nothing writes envelopes; when a writer opts in, that
    binding has to name the inner frame's CRC. Flagged in the code where it happens.
  • weft-server/tests/* cannot fabricate a frame fault any more than before, because
    WeftSegError is #[non_exhaustive] and weftdb::FrameFaultError has no public constructor: an
    end-to-end status test has to corrupt a real file on disk, which is what
    weft-server/tests/frame_fault.rs does. That is deliberate — a test cannot invent a status by
    constructing a fault it likes.
  • A bad format_version in the index row no longer changes behaviour, because the row is not asked.
    It is still recorded and still reported by the descriptor.

The plan's X-FRAME row (docs/release/1.0-plan.md:142) asks for a frame
registry, the v7 envelope, the read_frame*/describe_frame family and the
FrameFault classes, absorbing FRE-4, TAG-2 and ROB-8's fault_class naming.

weft_physical_type::frame_registry is now the only place that says which
frame version means what: 5 single-block, 6 paged, 7 envelope, and the rule
in the module doc that a number is never reused or renumbered once it has
shipped. The envelope is a header and a trailer around an existing frame,
WEFTSEG | u16 7 | u16 body_version | extension table | frame | crc32, and it
stores that frame verbatim, its own magic, version, body and CRC intact. That
is a deliberate constraint: segment_store.rs promises an unconfigured
deployment's bytes are byte-for-byte what previous versions wrote, and the
byte-pinning tests stay green because a v5 or v6 body is untouched. A legacy
reader pointed at an envelope still says UnsupportedVersion { found: 7 }
rather than misparsing it.

An extension carries a critical flag. Unknown and critical means refusal:
the body was written on the assumption that the extension would be applied,
so decoding without it serves data under a contract nobody honoured. Unknown
and optional means ignored. KNOWN_EXTENSIONS is empty on purpose - the
envelope ships with no extensions, and the numbers are a permanent namespace.

SegmentStore chose its decoder from the index row's format_version, so
anything that was not 6 - including the unwrap_or(0) in segment_index.rs -
sent a paged frame to the single-block reader. It now asks the file: the six
decode sites go through weftdb/src/types/frame_read.rs, which probes the
header, calls the matching read_frame_*, and returns failures as
weftdb::FrameFaultError in the anyhow chain under the unchanged
"decoding [paged ]segment <path>: ..." sentence. weft-server maps the class
by type: a frame ahead of this build is 503 incompatible_store, any other
frame fault is 500 frame_undecodable, the code X-ERR gave a wire sentence but
no producer.

No writer opts into 7 yet: seals still write 5 and 6. This slice is the
mechanism and the classes; the opt-in and the C5 quarantine behaviour belong
to D-S12 and the ingest lanes.

Tests: weft-physical-type 306 lib (14 new: envelope round-trip, a legacy
reader refusing an envelope, critical refusal, optional ignore, header/count
disagreement caught by CRC, describe_frame on a future version, fault
classification, the read family over a wrapped frame); weftdb frame_read 6
new; weft-server --test frame_fault 2 new, corrupting a real sealed frame
and asserting 500 frame_undecodable with details.fault "integrity" and 503
incompatible_store with "newer_format" after re-stamping the trailer CRC.
@physics515
physics515 merged commit e57e87d into xname/declare-validation Oct 21, 2026
18 checks passed
@physics515
physics515 deleted the xframe/frame-registry branch October 21, 2026 21:50
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