Standalone workspace member at poc/readplan/ that depends on alktype via path and exercises the ReadPlan/CompositePlan/ReadKind/ DiscriminatorPlan shape from ADR-011 against every BastType arm in the current read loop. Result: 28/30 tests pass. 2 deliberately ignored, both with documented findings: - Field-name-discriminator union read shape is a TODO (compile shape is correct; the read-side stub surfaces the work for implementation step 1 rather than hiding it). - Existing SequentialReader returns element_stride=0 for fixed-size struct arrays (pre-existing limitation at sequential_reader.rs:567, not a plan-shape gap; the POC plan correctly computes the stride). Coverage confirms every BastType arm compiles to the expected ReadKind/CompositePlan. Equivalence tests confirm plan-driven read produces identical (FieldValue, position) to the existing reader for all covered cases. ReadPlan: Send + Sync confirmed. Green light for ADR-011 implementation. See poc/readplan/FINDINGS.md for the full writeup. This branch is a derisking POC, not meant to merge to main (mirrors the bast-validator-poc branch pattern). Cargo.toml gains a workspace section that includes poc/readplan; that section is POC-only and would be dropped if these files ever merged to main.
6.9 KiB
ReadPlan POC — findings
Branch: readplan-poc
ADR: ADR-011
Status: ADR-011 accepted based on this POC. 28/30 tests pass; 2
deliberately ignored with documented findings.
Objective
Before accepting ADR-011 and starting implementation, derisk the
ReadPlan/CompositePlan/ReadKind/DiscriminatorPlan shape by:
- Walking every
BastTypearm in the current read loop (sequential_reader.rs:303-381) and confirmingReadPlan::compileproduces a plan that covers it. - Driving both the existing
SequentialReaderand a plan-driven reader over the same buffers and asserting identical(FieldValue, position)results.
If both pass, the shape is confirmed and implementation can proceed by
replacing the BastDoc walk in sequential_reader.rs and
materialize.rs with a ReadPlan walk.
What the POC is
poc/readplan/— a standalone workspace member (readplan-poccrate) that depends onalktypevia path dep.src/lib.rs—ReadPlan/FieldPlan/CompositePlan/ReadKind/DiscriminatorPlanmatching ADR-011's shape,ReadPlan::compilewalkingBastDoconce, andplan_read_field_at/plan_walk_struct_sizemirroring the existing read loop arm-by-arm.tests/coverage.rs—cov_*coverage tests (one perBastTypearm)eq_*equivalence tests (plan vs existingSequentialReader).
The POC is deliberately not production code: no doc comments on the plan types beyond the module header, no clippy-cleanliness gate, no bench. It exists to answer two questions and stop.
Result
28/30 tests pass. 2 ignored, both with documented findings.
Coverage — all BastType arms confirmed
Every BastType arm in the current read loop compiles to the expected
ReadKind/CompositePlan shape:
| BastType arm | ReadKind | CompositePlan | cov test |
|---|---|---|---|
| Primitive (Int8..Float64, Bool, String, Bytes) | Primitive(k) |
None |
cov_all_fixed_primitives, cov_string_and_bytes |
Enum (via $ref) |
Enum |
None |
cov_enum_ref |
| Struct (inline) | Struct |
Struct(ReadPlan) |
cov_struct_inline |
Struct (via $ref) |
Struct |
Struct(ReadPlan) |
cov_struct_ref |
| Union (byte disc) | Union |
Union { disc: Byte, variants } |
cov_union_byte_disc |
| Union (field disc) | Union |
Union { disc: Field, variants } |
cov_union_field_disc |
| Array (fixed elem) | Array |
Array { element, count, stride } |
cov_array_fixed_element |
| Array (variable elem, stride=0) | Array |
Array { element, count, stride: 0 } |
cov_array_variable_element_stride_zero |
Array ($ref elem) |
Array |
Array { element: Struct, count, stride } |
cov_array_ref_element |
| Record | Record |
Record { value: Struct-wrapped leaf } |
cov_record |
Field-level annotations (endian override, encoding, maxLength)
are preserved by compile_field — covered by cov_field_level_endian_override
and cov_maxlength_and_encoding_preserved.
Equivalence — plan == existing reader for every covered arm
eq_* tests drive both readers over the same buffer and assert
identical (FieldValue, position) for every field. All pass except
the two ignored ones below.
Send + Sync
ReadPlan: Send + Sync holds for the planned shape — confirmed by
readplan_is_send_sync. Falls out naturally from the plan being
immutable owned data with no lifetimes and no interior mutability.
Findings the POC surfaced
Finding 1 — Field-name-discriminator union read shape needs work
Status: POC TODO (ignored test eq_union_field_discriminator_todo).
compile_union correctly records DiscriminatorPlan::Field { name, field_index }
and the variant plans, but plan_read_union's Field arm is a stub
that returns an error. The field-name case is structurally different
from the byte-offset case: the union declares its own fields array
(the discriminator field + any shared fields), and the variant struct
is laid out after those shared fields. The plan needs a sub-struct
for the union's declared fields, separate from the variant plans.
This is not a plan-shape gap — DiscriminatorPlan::Field and the
variant ReadPlans compile correctly. It's a read-shape TODO that
the implementation step 1 must wire. The POC stubs it to surface the
work explicitly rather than hide it.
Finding 2 — Existing reader returns stride=0 for fixed-size struct arrays
Status: POC FINDING (ignored test eq_array_ref_element).
The existing SequentialReader returns element_stride: 0 for an
array of $ref-to-fixed-struct elements, even when the struct is
fixed-size (e.g. Point { x: u16, y: u16 } is 4 bytes). See
sequential_reader.rs:567:
let element_stride = if elem_kind.is_fixed_size() {
elem_kind.type_size().unwrap_or(0)
} else {
0
};
elem_kind is resolved_elem.alk_kind(), which returns
AlkTypeKind::Struct for a $ref to a struct. Struct.is_fixed_size()
is false, so the existing code returns 0. The POC's
fixed_struct_size helper correctly computes 4.
The implementation step must decide:
- (a) preserve the existing
stride=0behavior for back-compat (consumer walks sequentially), or - (b) fix the existing reader to return the true fixed-struct stride and let consumers index directly.
Either way, the ReadPlan shape is correct — this is a pre-existing
reader limitation, not a plan-shape gap. Recording it so the
implementation step makes a deliberate choice rather than inheriting
the old behavior by accident.
What this POC does not cover (out of scope, by design)
- Performance. No bench. The bench that matters lives in alktty's
wire_vs_bast.rs; the implementation commit re-runs it. The POC only proves correctness/coverage. - Aligned mode, validation, write side. ADR-011 scopes these out.
materialize_packedequivalence. The POC coversSequentialReaderequivalence;materialize_packeduses the sameBastTypearms viamaterialize_typeref_packedand will be covered by the implementation step's existingvalidate_bytestests.- Nested unions (union variant is itself a union). The POC's
compile_unionrejects this with a clear error. The existing reader supports it viaresolve_and_walk_variant'sBastDefKind::Unionarm; if a real schema needs it, the implementation step adds aVariantKind::Unionread path. Not blocking — no current schema exercises it.
Conclusion
ADR-011's ReadPlan shape covers every BastType arm and produces
identical results to the existing reader for all covered cases. The
two ignored tests document deliberate scope boundaries (field-disc
union read shape) and a pre-existing reader limitation (struct-array
stride), neither of which is a plan-shape gap.
Green light for ADR-011 implementation. The implementation step 1
(ReadPlan type + compile) can proceed, with the field-disc union
read shape and the struct-array stride decision as explicit sub-tasks.