feat(typedef): add implementation task decomposition (12 tasks, 7 generations)

Break the alknet-typedef architecture specs into atomic, dependency-ordered
implementation tasks covering crate init, error types, schema layer, data
access, both layout modes (aligned static + packed sequential), TUnion
dispatch, jsonschema custom keyword validators, TypedefEngine integration,
comprehensive tests, and a final review checkpoint.

Validated: 12 tasks, 0 cycles, 7 parallel generations.
This commit is contained in:
deepseek-v4-pro committed 2026-07-21 09:36:30 +00:00
1 parent dd232c3d47
commit db100f9849
12 files changed
+1996

No files matched your search

+181
View File
@@ -0,0 +1,181 @@
---
id: typedef/sequential-reader
name: Implement packed sequential SequentialReader for protocol read-side
status: pending
depends_on: [typedef/schema-types, typedef/error-type, typedef/data-access]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement the packed sequential `SequentialReader` in `crates/alknet-typedef/src/sequential_reader.rs`.
This is the read-side of Mode 1 (ADR-096): walks a buffer field-by-field according to
the schema, reading length prefixes to determine variable-length data positions. Used
at read time when the consumer is parsing an incoming frame.
Per [layout-engine.md](../../docs/architecture/crates/typedef/layout-engine.md) §"Mode 1: Packed sequential".
### Target shape
```rust
/// Walks a buffer field-by-field according to a schema, reading length
/// prefixes to determine variable-length data positions. Used at read time
/// when parsing incoming protocol frames.
///
/// The reader is sequential — it cannot jump to field N without reading
/// fields 0..N-1 first. This is inherent to packed layouts where
/// variable-length fields shift subsequent fields.
#[derive(Debug)]
pub struct SequentialReader {
/// The schema being read.
schema: serde_json::Value,
/// The endianness for the layout.
endian: Endian,
}
/// A value read from a field during sequential traversal.
#[derive(Debug)]
pub enum FieldValue<'a> {
I8(i8),
I16(i16),
I32(i32),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
F32(f32),
F64(f64),
Bool(bool),
Enum(u32),
String(&'a str),
Bytes(&'a [u8]),
/// A nested struct — the consumer recurses with a new SequentialReader
/// scoped to the struct's byte range.
Struct { start: usize, end: usize },
/// A union — the consumer reads the discriminator, then recurses
/// with the variant schema.
Union { discriminator: String, variant_start: usize },
/// An array — the consumer iterates elements.
Array { count: u32, element_start: usize, element_stride: usize },
}
impl SequentialReader {
/// Create a new SequentialReader from a schema.
pub fn new(schema: &serde_json::Value) -> Result<Self, TypedefError>;
/// Read the next field from the buffer at the current position.
/// Returns the field name, the value, and advances the internal position.
/// Returns `None` when all fields have been read.
pub fn read_next<'a>(
&mut self,
buffer: &'a [u8],
) -> Result<Option<(String, FieldValue<'a>)>, TypedefError>;
/// Read a specific field by path. This walks through all preceding fields
/// to reach the target (sequential access is inherent to packed layouts).
pub fn read_field<'a>(
&mut self,
buffer: &'a [u8],
field_path: &str,
) -> Result<FieldValue<'a>, TypedefError>;
/// Reset the reader to the beginning of the buffer.
pub fn reset(&mut self);
/// The current byte position in the buffer.
pub fn position(&self) -> usize;
}
```
### How it works
For a struct with fields `[u8, u32, string]`:
```
SequentialReader:
read_next() → ("field_0", FieldValue::U8(42)), position = 1
read_next() → ("field_1", FieldValue::U32(1234)), position = 5
read_next() → reads u32 length prefix at offset 5 → data_len = 10
("field_2", FieldValue::String("hello worl")), position = 19
read_next() → None (no more fields)
```
The reader walks the buffer sequentially. It reads each field's type from the schema,
reads the appropriate number of bytes at the current position, and advances. For
variable-length fields, it reads the 4-byte length prefix to determine the data
extent, then advances past the data.
### Nested structs
When the reader encounters a `TStruct` field, it returns `FieldValue::Struct { start, end }`.
The consumer creates a new `SequentialReader` scoped to that byte range and reads the
inner fields.
### TUnion
When the reader encounters a `TUnion` field:
1. For byte-offset discriminators: reads the discriminator value at the known offset,
looks up the variant schema, returns `FieldValue::Union { discriminator, variant_start }`.
2. For field-name discriminators: reads the discriminator field like any other field,
then returns the union value.
### TArray
When the reader encounters a `TArray` field:
1. Reads the count (from `minItems`/`maxItems` if fixed, or from a 4-byte count prefix
if variable).
2. Returns `FieldValue::Array { count, element_start, element_stride }`.
3. The consumer iterates elements using the stride.
### What this does NOT include
- The write-side of packed mode (that's `layout_builder.rs`)
- Aligned static layout (that's `offset_map.rs`)
- TUnion discriminator dispatch (that's `tunion.rs`)
- The `TypedefEngine` struct (that's `engine.rs`)
## Acceptance Criteria
- [ ] `SequentialReader` struct with `new(schema)` constructor
- [ ] `read_next(buffer)` reads the next field and advances position
- [ ] Fixed-size fields read correct number of bytes at current position
- [ ] Variable-length fields: reads 4-byte length prefix, then skips data
- [ ] Position advances correctly through all fields
- [ ] `read_next()` returns `None` when all fields have been read
- [ ] `read_field(field_path)` walks through preceding fields to reach target
- [ ] `reset()` resets position to beginning
- [ ] `position()` returns current byte offset
- [ ] Nested structs: returns `FieldValue::Struct { start, end }` for consumer recursion
- [ ] `TUnion`: reads discriminator, returns `FieldValue::Union { ... }`
- [ ] `TArray`: reads count, returns `FieldValue::Array { count, element_start, element_stride }`
- [ ] Endianness respected for all multi-byte reads
- [ ] Returns `TypedefError::Access` for buffer-too-short
- [ ] Returns `TypedefError::Schema` for malformed schemas
- [ ] No `unwrap()` or `expect()` on error paths
- [ ] All public types and functions have doc comments
- [ ] `cargo check -p alknet-typedef` succeeds
- [ ] `cargo clippy -p alknet-typedef` succeeds with no warnings
- [ ] `cargo build --workspace` still succeeds
## References
- docs/architecture/crates/typedef/layout-engine.md — Mode 1: Packed sequential, SequentialReader
- docs/architecture/crates/typedef/data-access.md — read/write functions used by the reader
- docs/architecture/decisions/096-two-layout-modes-packed-vs-aligned.md — ADR-096
- docs/architecture/decisions/097-schema-annotations.md — ADR-097 (encoding, discriminators)
- /workspace/alknet-typedef-poc/src/offset.rs — POC reference (SequentialReader in POC 2)
## Notes
> This is the read-side of the packed sequential mode. The reader walks the buffer
> sequentially — it cannot jump to field N without reading fields 0..N-1 first.
> This is inherent to packed layouts where variable-length fields shift subsequent
> fields. The POC 2's `SequentialReader` is a good reference. The reader uses the
> `data_access` module's read functions internally.
## Summary
> To be filled on completion