Expand description
Dynamic — reflective value tree for schema migration.
Dynamic is the bridge that lets a Migrate
impl read fields out of an older stored record without round-tripping
through every intermediate type. It is intentionally heavyweight (one
allocation per node); migration is a cold path and we trade speed for
reflective access.
§Wire format — design choice
postcard is not self-describing — there is no way to reconstruct
field names or even the structural shape of a previously-written
postcard payload from the bytes alone. Two paths are available:
- Pair every
Documentwith a hand-writtenpostcard::experimental::schema::Schemadescription and use that to walk the bytes. - Define an obj-internal “tagged Dynamic” wire format and round-trip via that.
M5 picks (2). Rationale: option (1) couples obj to postcard’s
experimental-schema API which has neither stability nor MSRV
guarantees; option (2) is fully under our control, is auditable in
100 lines of code, and never leaks onto the disk image of normal
documents (only migration code paths see it). The on-disk shape is
documented in docs/format.md § Postcard pin — Obj-internal
extension.
Dynamic is not an on-disk encoding for application documents.
Application code that writes documents through
codec::encode writes native postcard, NOT
the tagged format. Dynamic only appears in flight during a migration
and is discarded as soon as the migrated record reaches its
caller-visible type.
§M10: schema-driven decode
Dynamic::from_postcard_bytes decodes a native-postcard payload
into a structured Dynamic using a caller-supplied
DynamicSchema. This is the
path the codec takes when reading a v1 record through a v2
reader: it consults Document::historical_schemas() (M10 #82)
for the v1 schema, walks the bytes per that schema, and hands
the resulting Dynamic to Migrate::migrate so the v2 author
can read fields by name. The walker is iterative
(Vec<Frame> stack — Rule 1) and bounded by
MAX_SCHEMA_DEPTH.
The obj-internal tagged-Dynamic format remains available via
Dynamic::from_tagged_bytes / Dynamic::to_postcard_bytes
for forensic logging of an in-flight Dynamic.
§Power-of-ten posture
- Rule 1. Recursive walks (
get,set,Dynamic::deserialize) use an explicit stack — no Rust-language recursion. Depth is bounded byMAX_DYNAMIC_DEPTH(32); exceeding it returnsError::Corruption. - Rule 2. Each tagged-decode call reads at most
MAX_DYNAMIC_NODESnodes (defense-in-depth against a forged payload claiming a huge sequence length). - Rule 5. Tag bytes are validated against the documented set; any
unknown tag is
Error::Corruption. - Rule 7. No
unwrap/expecton any error-bearing path.
Enums§
- Dynamic
- A postcard-decoded view of a stored record.
Constants§
- MAX_
DYNAMIC_ DEPTH - Maximum depth of a
Dynamictree. Defense-in-depth bound that stops a forged payload from triggering unbounded growth. - MAX_
DYNAMIC_ NODES - Maximum total node count in a
Dynamictree. Defense-in-depth bound on the worst-case allocation a malformed payload could trigger.