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

+183
View File
@@ -0,0 +1,183 @@
---
id: typedef/schema-types
name: Implement TypeDef kind detection, schema annotation parsing, $ref normalization, and Endian enum
status: pending
depends_on: [typedef/crate-init]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement the schema layer in `crates/alknet-typedef/src/schema.rs`. This module
provides the foundational types and functions that every other module depends on:
TypeDef kind detection, type size constants, schema annotation parsing, `$ref`
normalization, and the `Endian` enum.
Per [schema-layer.md](../../docs/architecture/crates/typedef/schema-layer.md) and
[ADR-097](../../docs/architecture/decisions/097-schema-annotations.md).
### TypeDef kind detection
The engine needs to identify which `TypeDef:*` kind a schema node declares. The
17 kinds (16 from TypeBox's `typedef.ts` + `TypeDef:Bytes` added by alknet-typedef):
| Kind | Keyword | Category | Byte size |
|------|---------|----------|-----------|
| Float32 | `TypeDef:Float32` | fixed | 4 |
| Float64 | `TypeDef:Float64` | fixed | 8 |
| Int8 | `TypeDef:Int8` | fixed | 1 |
| Int16 | `TypeDef:Int16` | fixed | 2 |
| Int32 | `TypeDef:Int32` | fixed | 4 |
| Uint8 | `TypeDef:Uint8` | fixed | 1 |
| Uint16 | `TypeDef:Uint16` | fixed | 2 |
| Uint32 | `TypeDef:Uint32` | fixed | 4 |
| Boolean | `TypeDef:Boolean` | fixed | 1 |
| Enum | `TypeDef:Enum` | fixed | 4 (u32 index) |
| String | `TypeDef:String` | variable | — |
| Bytes | `TypeDef:Bytes` | variable | — |
| Struct | `TypeDef:Struct` | composite | sum of fields |
| Union | `TypeDef:Union` | composite | discriminator + variant |
| Array | `TypeDef:Array` | composite | count × element |
| Record | `TypeDef:Record` | variable | — |
| Timestamp | `TypeDef:Timestamp` | variable | — |
Implement:
```rust
/// Returns the `TypeDef:*` kind string if the schema node declares one.
/// Returns `None` if the node has no `TypeDef:*` keyword.
pub fn get_typedef_kind(node: &serde_json::Value) -> Option<&str>;
/// Returns the fixed byte size for a TypeDef kind, or `None` if variable-size.
pub fn type_size(kind: &str) -> Option<usize>;
/// Returns the natural alignment for a TypeDef kind.
pub fn natural_alignment(kind: &str) -> usize;
/// Returns true if the kind is a fixed-size type.
pub fn is_fixed_size(kind: &str) -> bool;
```
### Endian enum
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endian {
Little,
Big,
}
impl Endian {
/// Parse from the schema's "endian" annotation. Defaults to Little.
pub fn from_schema(schema: &serde_json::Value) -> Self;
}
```
### Schema annotation parsing
Parse the annotations decided in ADR-097:
```rust
/// Parse the "encoding" annotation from a variable-length type's keyword value.
/// The keyword value may be `true` (shorthand for length-prefixed) or an object
/// with an "encoding" field.
pub fn parse_encoding(keyword_value: &serde_json::Value) -> VariableEncoding;
pub enum VariableEncoding {
LengthPrefixed,
OffsetIndirect,
}
/// Parse the "align" annotation from a schema node. Returns None if not specified.
pub fn parse_align(node: &serde_json::Value) -> Option<usize>;
/// Parse the "maxLength" annotation (standard JSON Schema keyword).
pub fn parse_max_length(node: &serde_json::Value) -> Option<usize>;
/// Parse the "endian" annotation. Defaults to Little if absent or unrecognized.
pub fn parse_endian(node: &serde_json::Value) -> Endian;
```
### TUnion discriminator parsing
Parse the discriminator shape from a `TypeDef:Union` schema node:
```rust
pub enum DiscriminatorKind {
Byte { offset: usize, disc_type: String },
Field { name: String },
}
/// Parse the "discriminator" annotation from a TUnion schema node.
pub fn parse_discriminator(node: &serde_json::Value) -> Result<DiscriminatorKind, TypedefError>;
```
### `$ref` normalization
TypeBox generates bare-name `$ref` values (e.g., `"$ref": "Read"`). The `jsonschema`
crate requires full JSON Pointer paths (e.g., `"$ref": "#/$defs/Read"`). Normalize
at schema load time:
```rust
/// Walk the schema tree. For every "$ref" whose value is a bare name
/// (no "#" prefix), rewrite it to "#/$defs/<name>".
/// Full JSON Pointer refs pass through unchanged. Idempotent.
pub fn normalize_refs(schema: &mut serde_json::Value);
```
This is a ~20-line recursive walk. It runs once at load time, before the schema is
passed to `jsonschema::validator_for` or the offset computation.
### What this does NOT include
- The actual offset computation (that's `offset_map.rs` and `layout_builder.rs`)
- The read/write functions (that's `data_access.rs`)
- The custom keyword validators (that's `validation.rs`)
- The `TypedefEngine` struct (that's `engine.rs`)
## Acceptance Criteria
- [ ] `get_typedef_kind()` correctly identifies all 17 `TypeDef:*` kinds
- [ ] `type_size()` returns correct byte sizes for all fixed-size types
- [ ] `type_size()` returns `None` for variable-length and composite types
- [ ] `natural_alignment()` returns correct alignment for each kind
- [ ] `Endian` enum with `Little` and `Big` variants
- [ ] `Endian::from_schema()` defaults to `Little` when annotation is absent
- [ ] `Endian::from_schema()` returns `Big` when `"endian": "big"`
- [ ] `parse_encoding()` handles both `true` (shorthand) and `{ "encoding": "..." }` (object)
- [ ] `parse_encoding()` defaults to `LengthPrefixed` when annotation is absent
- [ ] `parse_align()` returns `None` when no `"align"` annotation
- [ ] `parse_max_length()` returns `None` when no `"maxLength"` annotation
- [ ] `parse_discriminator()` handles byte-offset (`kind: "byte"`) and field-name (`kind: "field"`)
- [ ] `parse_discriminator()` returns `Err(TypedefError::Schema(...))` for malformed discriminators
- [ ] `normalize_refs()` rewrites `"$ref": "Read"` → `"$ref": "#/$defs/Read"`
- [ ] `normalize_refs()` leaves `"$ref": "#/$defs/Read"` unchanged (idempotent)
- [ ] `normalize_refs()` handles nested objects and arrays recursively
- [ ] All public 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/schema-layer.md — the 17 TypeDef kinds, annotations, $ref normalization
- docs/architecture/decisions/097-schema-annotations.md — ADR-097 (concrete JSON shapes)
- docs/architecture/decisions/095-alknet-typedef-purpose-scope-jsonschema-engine.md — ADR-095
- docs/research/alknet-typedef/findings.md — POC results, open questions
- /workspace/alknet-typedef-poc/src/offset.rs — POC reference for kind detection
## Notes
> This is the foundational types module. Every other module depends on it for
> TypeDef kind identification, size lookups, and annotation parsing. The `$ref`
> normalization bridges TypeBox output to jsonschema input — without it, bare-name
> refs from TypeBox would fail to resolve. The `TEnum` kind uses a `u32` index
> (not a variable-length string) — a deliberate deviation from TypeBox fidelity
> for binary efficiency, per the schema-layer spec.
## Summary
> To be filled on completion