Skip to main content

Module dynamic

Module dynamic 

Source
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:

  1. Pair every Document with a hand-written postcard::experimental::schema::Schema description and use that to walk the bytes.
  2. 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 by MAX_DYNAMIC_DEPTH (32); exceeding it returns Error::Corruption.
  • Rule 2. Each tagged-decode call reads at most MAX_DYNAMIC_NODES nodes (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 / expect on any error-bearing path.

Enums§

Dynamic
A postcard-decoded view of a stored record.

Constants§

MAX_DYNAMIC_DEPTH
Maximum depth of a Dynamic tree. Defense-in-depth bound that stops a forged payload from triggering unbounded growth.
MAX_DYNAMIC_NODES
Maximum total node count in a Dynamic tree. Defense-in-depth bound on the worst-case allocation a malformed payload could trigger.