Resolve N3: maxLength is string/bytes-only (review #006)
- Parse gate in BastField::parse: maxLength on any kind other than
string/bytes is a clean Schema error (records, arrays, inline
structs, refs, union shared fields all covered; the choke point
needs no ref-following since $defs entries are struct/union/enum)
- Meta-schema FieldDef: if kind in {string, bytes} else maxLength
forbidden — the published alk.dev/bast/v1 contract matches the
parser (N2 dual-layer pattern)
- M5's compute-side record maxLength arm became unreachable and was
deleted (offset-indirect arm stays); the two superseded M5
maxLength tests rewritten as the n3_* parse-rejection family
- ADR-006 remedy message tailored per kind: for records both
annotated remedies are dead ends, so the error text points at the
last-position fix only
- Docs aligned: bast-format.md (FieldDef meta-schema + FieldDef/
Variable-Length Encoding prose), layout-engine.md (Strategy 2 +
ADR-006 paragraph), schema-layer.md, ADR-003 §2/§3a amended,
builder .max_length() doc
- Review #006: N3 resolved (all findings now closed), M5 update
note, test-count bookkeeping note (in-session probes vs static
counts), status lines flipped to fully resolved
Verified: 547 tests green + 2 ignored doctests in BOTH release and
default profiles (a stale debug artifact from an earlier session
masked one H3 roundtrip test in debug; clean rebuild passes both),
clippy -D warnings clean, cargo doc --no-deps zero warnings, wasm
build green.
This commit is contained in:
1 parent
0857ea1c23
commit
bb28ba3006
9 files changed
+352
-95
No files matched your search
@@ -128,6 +128,12 @@ These are different validators for different inputs.
|
||||
"encoding": { "enum": ["length-prefixed", "offset-indirect"] },
|
||||
"maxLength": { "type": "integer", "minimum": 0 }
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"kind": { "enum": ["string", "bytes"] }
|
||||
}
|
||||
},
|
||||
"else": { "properties": { "maxLength": false } },
|
||||
"required": ["name", "kind"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -269,8 +275,11 @@ These are different validators for different inputs.
|
||||
- `align` (optional): field-level alignment (aligned mode only).
|
||||
- `encoding` (optional): `"length-prefixed"` (default) or
|
||||
`"offset-indirect"`. See [Variable-length encoding](#variable-length-encoding).
|
||||
- `maxLength` (optional): byte-length cap. See
|
||||
[Variable-length encoding](#variable-length-encoding).
|
||||
- `maxLength` (optional, `string`/`bytes` fields only): byte-length
|
||||
cap. See [Variable-length encoding](#variable-length-encoding).
|
||||
Rejected at parse on any other kind (review #006 N3: the annotation
|
||||
was silently unenforced there — the validation plan bakes `maxLength`
|
||||
into string/bytes leaves only).
|
||||
|
||||
### TypeRef
|
||||
|
||||
@@ -456,8 +465,11 @@ override). In little-endian mode, `u32::from_le_bytes`; in big-endian
|
||||
mode, `u32::from_be_bytes`. Ensures SFTP consumers (big-endian) have
|
||||
consistent byte order for field values and length prefixes.
|
||||
|
||||
Applies to all variable-length types: `string`, `bytes`,
|
||||
`record`, and arrays of variable-length elements.
|
||||
Applies to variable-length primitive types only: `string` and
|
||||
`bytes`. The parser rejects `maxLength` (and the meta-schema forbids
|
||||
it) on every other kind — including `record` (review #006 N3/M5: no
|
||||
consumer honored it there, so the annotation was either silently
|
||||
unenforced or, in aligned mode, silently corrupt).
|
||||
|
||||
## Endianness
|
||||
|
||||
|
||||
@@ -136,9 +136,9 @@ reserving worst-case space.
|
||||
|
||||
- `true` is a shorthand for the default (length-prefixed). This keeps
|
||||
the common case concise and the override explicit.
|
||||
- The `encoding` annotation and `maxLength` apply to all variable-length
|
||||
types: `AlkType:String`, `AlkType:Bytes`, `AlkType:Array`,
|
||||
`AlkType:Record`, `AlkType:Timestamp`.
|
||||
- The `encoding` annotation and `maxLength` apply to the variable-length
|
||||
primitive types `AlkType:String` and `AlkType:Bytes`. (`maxLength` on
|
||||
records was amended out by review #006 N3/M5 — see §3a.)
|
||||
|
||||
### 3a. TRecord value type
|
||||
|
||||
@@ -164,8 +164,13 @@ the `"values"` property in the schema:
|
||||
the value's size is determined by its kind (fixed-size kinds have a
|
||||
known size; variable-length kinds carry their own length prefix).
|
||||
- The count and key-length prefixes respect the schema's endianness.
|
||||
- In aligned static mode with `maxLength`, the entire record is reserved
|
||||
at `maxLength` bytes (zero-padded).
|
||||
- ~~In aligned static mode with `maxLength`, the entire record is
|
||||
reserved at `maxLength` bytes (zero-padded).~~ **Amended (review #006
|
||||
N3/M5, 2026-09-02):** `maxLength` is rejected at parse on record
|
||||
fields. The aligned materializer walks the record's inline
|
||||
count-prefixed form and never honors the reservation (M5: silent
|
||||
cross-field corruption), and no packed consumer enforced it either
|
||||
(N3: silently unenforced). `maxLength` is `string`/`bytes`-only.
|
||||
|
||||
### 4. TUnion discriminators
|
||||
|
||||
|
||||
@@ -113,7 +113,11 @@ with a `AlkTypeError::Offset` — the `OffsetMap` reserves only 4 bytes
|
||||
(the length prefix), but `data_access::write_string` writes prefix +
|
||||
data inline, which would clobber subsequent fields. Non-final variable
|
||||
fields must use `maxLength` (fixed-size reservation) or
|
||||
`"encoding": "offset-indirect"`. See
|
||||
`"encoding": "offset-indirect"` — except `record` fields, for which
|
||||
neither remedy is available (`maxLength` is rejected at parse — review
|
||||
#006 N3 — and `offset-indirect` is rejected for records in aligned
|
||||
mode — review #006 M5), so a non-final record field cannot be repaired
|
||||
and must move to the last position. See
|
||||
[ADR-006](decisions/006-reject-non-final-inline-length-prefixed-in-aligned-mode.md).
|
||||
|
||||
## Offset Computation Algorithm
|
||||
@@ -194,6 +198,12 @@ annotation shapes).
|
||||
only. The engine uses strategy 1 (inline length-prefixing) because
|
||||
protocols don't benefit from fixed-size reservation.
|
||||
|
||||
`maxLength` applies to `string` and `bytes` fields only. The parser
|
||||
rejects it on any other kind (review #006 N3): the validation plan
|
||||
bakes it into string/bytes leaves only, so on a record (or any other
|
||||
kind) the annotation did nothing — and in aligned mode a record
|
||||
reservation was silently corrupt (review #006 M5).
|
||||
|
||||
**Strategy 3: Offset indirection (`"encoding": "offset-indirect"`).**
|
||||
1. The field is a struct `{offset: u32, length: u32}`.
|
||||
2. The `OffsetMap` records the position of this struct.
|
||||
|
||||
@@ -230,7 +230,10 @@ type-level properties. The concrete BAST shapes are in
|
||||
The `maxLength` keyword is *not* a BAST invention — it is the standard
|
||||
JSON Schema `maxLength`, repurposed as a byte-length cap. In aligned
|
||||
mode it reserves a fixed-size slot; in packed mode it is a validation
|
||||
constraint only. See [bast-format.md §Variable-Length
|
||||
constraint only. It applies to `string`/`bytes` fields only: the parser
|
||||
rejects it on any other kind (review #006 N3 — elsewhere it was
|
||||
silently unenforced), and in aligned mode a record reservation was
|
||||
silently corrupt (review #006 M5). See [bast-format.md §Variable-Length
|
||||
Encoding](bast-format.md#variable-length-encoding) and
|
||||
[ADR-003](decisions/003-schema-annotations.md).
|
||||
|
||||
|
||||
Reference in new issue
Block a user