Resolve all 11 findings from the 0.3.0 plan review (#005) in one
docs-only pass. No source changes; the crate still builds/tests at
v0.2.0. The one substantive decision change is M3 (per user
direction: ship ValidationPlan in 0.3.0, no more hedging); the rest
are spec corrections or pre-implementation refinements to types that
do not yet exist on main.
- H1: refine ADR-011 CompositePlan::Union to carry
shared: Option<Box<ReadPlan>> (field-disc shared fields) and
variants: Vec<(String, CompositePlan)> (drop VariantPlan/
VariantKind). Plan phase 1 implements the refined shape.
- H2: plan phase 2 specifies ReadPlan stores schema: Arc<Value>
(not &Value), avoiding the self-referential struct ADR-011
rejects. Verified serde_json::Value: Hash + Eq holds with
preserve_order, so phase 6 derives are not blocked.
- M1: nested-union support falls out of the H1 shape refinement
(a variant can be CompositePlan::Union) — option (a) from the
review, no behavioral drop vs 0.2.0, no Semver regression row.
- M2: plan phase 5 adds an explicit first sub-step to derive Hash
on Endian and VariableEncoding in src/schema.rs (additive,
semver-safe prerequisite the original plan omitted).
- M3: reverse the ValidationPlan deferral. ADR-012's "Deferring
ValidationPlan" becomes "ValidationPlan — in scope for 0.3.0";
new ADR-012 §3 commits the decision (compiled form, no per-buffer
BastDoc walk, Hash + Eq + fingerprint()) and defers only the
concrete shape to a follow-on design session + the plan's new
phase 7. Plan gains phase 7 (ValidationPlan); old phase 7 (bump)
renumbered to phase 8. ADR-011's Out-of-scope and Scope
Boundaries bullets updated to point at ADR-012 §3. The deferral
black hole this review's methodology flagged is closed: the work
is committed with a concrete reactivation trigger, not hedged
into an unplanned future.
- L1: plan phase 2 corrects the dummy_field_for/ty_source removal
claim — only packed-side call sites go away; the helpers stay
for the aligned materialize_leaf_at path.
- L2: plan phase 2 states the packed-vs-aligned
materialize_typeref_packed split (packed gets a new plan-walking
function; the existing function stays for aligned).
- L3: plan phase 5 adds a Scope Boundary note — aligned
materialize's BastDoc structure walk is the permanent 0.3.0
design; an AlignedPlan is out of scope, tracked as an OQ.
- N1: fix "back-comat" -> "back-compat" typo.
- N2: plan phase 1 verification adds the read_plan_is_send_sync
static-bound assertion test ADR-011 requires.
- N3: Semver Contract table notes the Result drop on
SequentialReader::new (Result<Self, AlkTypeError> -> Self)
alongside the argument-type change.
Also: ADR-012 title -> "Plan Fingerprinting, ValidationPlan, and
Closing the Deferred M1 Sites in 0.3.0"; §3 (Fingerprinting
OffsetMap) renumbered to §4; README ADR table updated; review #005
gets a Resolution section recording how each finding was closed.
Verification (docs-only change, v0.2.0 unchanged):
cargo test --release ok (310 crate + 86 integration + 2 doctests)
cargo clippy --all-targets -- -D warnings ok
cargo doc --no-deps ok
Cross-checks docs/plans/030-compiled-forms.md against the codebase,
ADRs 011/012, the POC on readplan-poc, and review #004.
Findings:
- H1: field-disc union shape is in neither ADR-011 nor the POC
- H2: schema() &Value on Arc<ReadPlan> is the self-referential
pattern ADR-011 rejects
- M1: nested-union silent behavioral drop (POC rejects what 0.2.0
accepts); deferral-black-hole pattern
- M2: Endian/VariableEncoding missing Hash derive (phases 5/6 break)
- M3: ValidationPlan deferral flagged for re-evaluation — the
read+validate-on-untrusted-input case may be hotter than
ADR-012's 'not a hot loop' dismissal accounts for
- L1/L2/L3: dummy_field_for wording, materialize_packed split,
materialize_aligned BastDoc walk silence
- N1/N2/N3: typo, Send+Sync assertion test, Result drop on new
Includes a deferral-pattern scan methodology section surfacing
M1/L3/H2 as black-hole instances and confirming the plan's four
explicit deferred decisions are the healthy pattern.
Verification: file-only change, no code touched.
ADR-012 bundles two pieces of work into the 0.3.0 release so the
crate ships one round of breaking changes, not two:
- Fingerprinting: #[derive(Hash, Eq)] + fingerprint() -> u64 on
ReadPlan and OffsetMap. BTreeMap for ReadPlan.by_name (HashMap
blocks Hash derive). Fingerprint contract: equal hashes => identical
reads over identical bytes. Enables cross-run plan caching, alkcall
hub/spoke schema handshake, schema-version diagnostics.
- Closing the deferred M1 sites via owned BastDoc (lifetime removal,
scoped to LayoutBuilder/bast_validation/materialize_aligned/
OffsetMap::compute) + extending OffsetMap with LeafMeta
{kind, encoding, endian} for the aligned read_field/write_field paths.
Reframes the 'WritePlan' candidate from ADR-011's Future capabilities
section: the packed write-side compiled form is PackedLayout; the
aligned R/W compiled form is OffsetMap; the M1 fixes are 'cache the
parse' and 'extend the compiled form with leaf metadata', not 'add a
third compiled form.' Serves minimal-public-API-changes better than
a literal WritePlan type. ValidationPlan deferred (different shape,
not a hot loop).
The plan (docs/plans/030-compiled-forms.md) is the execution entry
point: seven phases ordered by dependency, each phase a session
boundary. Phase 1-2: ReadPlan (ADR-011). Phase 3: owned BastDoc.
Phase 4: LayoutBuilder M1 fix. Phase 5: OffsetMap LeafMeta. Phase 6:
fingerprinting. Phase 7: version bump + docs + verification. Includes
a semver contract table, deferred decisions, cross-phase invariants,
and the verification block.
ADR-011's Future capabilities section updated to point at ADR-012 for
the items moving into 0.3.0 and record the WritePlan reframe. README
ADR table gets ADR-012 as Proposed.
Verification: docs-only change; cargo test --release, cargo clippy
--all-targets -- -D warnings, cargo doc --no-deps unchanged (no source
touched).
Tighten framing per pre-acceptance review (no decision changes):
- Be precise about M1 coverage: closes the packed-side site
(engine.rs:284 validate_bytes); the aligned-side M1 sites
(engine.rs:334,467, layout_builder.rs:190) are a deliberate
reversible bet, not a non-issue.
- Replace the 'two type walkers is symmetric with OffsetMap/
PackedLayout' spin with an honest 'parallel typed tree, permanent
maintenance tax, justified by ~20-50x composite-dispatch win on
SFTP-shaped union-with-$ref-variants packets.'
- Move the 'determinism enables fingerprinting/cache/handshake' future
work out of the positives list into a dedicated 'Future
capabilities' section — it is a forward reference, not a current win.
- Clarify bast_doc: Value is retained unconditionally (unused in
packed mode, still needed in aligned mode); add a Send+Sync test
note for Arc<ReadPlan> shared from the Send+Sync engine.
- Tighten the 'no format! allocations' claim to 'no resolve_typeref,
no BastDef::parse, no JSON node access on the happy path' (error-
path format! remains, and is not the cost being removed).
- Add a 'POC coverage' section recording that the readplan-poc branch
walked every BastType arm and confirmed ReadPlan covers all cases,
including the union Byte/Field split and the array variable-stride
(element_stride = 0) case.
Status flipped Proposed -> Accepted. README ADR table updated.
Verification: docs-only change; cargo test --release, cargo clippy
--all-targets -- -D warnings, cargo doc --no-deps unchanged (no source
touched).
Review #004 measured the packed read path at ~400x slower per chunk
than a hand-rolled codec, root-caused to SequentialReader re-parsing
BastDoc::new on every field read (the 're-parse on demand' framing
from ADR-007). The write path is competitive because it has a compiled
form (PackedLayout); the read path is the only mode/side pair without
one.
ADR-011 proposes ReadPlan — the packed read-side compiled form,
symmetric to OffsetMap (aligned R/W) and PackedLayout (packed W).
Packed positions are data-dependent (variable-length fields shift
subsequent fields), so the compiled form is necessarily a read program
(a pre-resolved tree of read instructions), not a flat lookup table
like OffsetMap. ReadPlan::compile walks BastDoc once at engine
construction, resolves all $ref\s eagerly, computes effective
endianness at every node, and inlines union variants; the read loop
then indexes into a Vec, matches on ReadKind, and calls data_access
with a precomputed Endian — no BastDoc, no resolve_typeref, no JSON
node access at read time.
Scope: SequentialReader and materialize_packed consume the plan (one
walker, not two); bast_validation, LayoutBuilder, and aligned one-shot
paths stay on BastDoc (different concern, not hot loops). Includes a
7-step Recommended Order matching review #004's structure. Breaking
public-API change (0.2.0 -> 0.3.0): SequentialReader::new and
materialize_packed take ReadPlan; BastDoc and the Bast* types are
unchanged (smaller breakage than review #004's Option A).
Cross-links: ADR-007's 'Cost' section gets a Note pointing at review
#004 and ADR-011, marking the 're-parse on demand' framing as the root
cause slated for retirement (the factory decision itself is retained);
the actual Cost/doc-comment rewrite happens in the implementation
commit per ADR-011's recommended order. README ADR table updated.
Verification: docs-only change, no source touched.
Closes review #004 H1, M1 (packed side), L1, L2 (on implementation).
Traces the ~400x read-path gap (alktty wire_vs_bast bench) to
SequentialReader::read_field_at re-parsing BastDoc::new on every field
read, with the self-referential lifetime constraint as the root cause.
Findings:
- H1: per-field BastDoc::new re-parse in sequential_reader.rs:262
- M1: same re-parse in four one-shot paths (LayoutBuilder::build,
validate_bytes, engine read_field/write_field)
- L1: dead _field_schema param + Value clones in SequentialReader
- L2: ADR-007 Cost section + engine doc comment understate the re-parse
- N1: carry-forward of review #003 N2 (no new action)
Lays out fix options: Option A (owned typed tree, recommended, closes
H1+M1, breaking), Option B (read-plan precompute, fallback, H1 only,
non-breaking), Option C (borrow-from-engine, rejected, contradicts
ADR-007).
Verification: docs-only review; alktype source unchanged.
cab4932
- README: 19 -> 18 kinds, drop the timestamp row from the kinds table,
drop 'timestamp shape' from the validate_bytes constraint list, remove
the residual 'upcoming alkcall crate' sentence from the crate
independence section (alkcall exists now), fix two '19 kinds' refs in
the documentation pointer list and schema-layer link.
- src/data_access.rs: module doc comment still said 'all 19 AlkType
kinds' -> 18.
- bast-format.md (normative): 19 -> 18 AlkTypeKind enum variants.
- layout-engine.md: cross-reference to 'the 19 AlkType kinds' -> 18.
- ADR-BAST (bast-bast-format.md): the Decision section claimed the post-
pivot enum has '19 unchanged' variants; now 18, with the wording
adjusted so it no longer says 'unchanged' across the pivot.
- ADR-VAL-SPLIT: drop 'timestamp shape' from the value-domain constraint
list and the validator-arm table row (validate_timestamp no longer
exists).
Left as historically accurate (describe the v0.1.0 pre-pivot state):
ADR-003/004/006 Context mentions of AlkType:Timestamp, ADR-005 'engine
now has 19', and the '19 jsonschema::Keyword factories' references in
the What-is-removed sections of ADR-BAST and ADR-VAL-SPLIT.
Verification: cargo test --release (407 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean), cargo build --target
wasm32-unknown-unknown --release (clean).
The Timestamp kind was a residual from an early research reference. It
was byte-identical to String everywhere (length-prefixed UTF-8) and its
only distinguishing behavior was a hand-rolled non-strict RFC 3339 check
that the docs admitted was incomplete (Feb 31 passes, seconds range
unchecked, no leap seconds). JSON-level timestamp validation is
jsonschema's job (format: date-time on the validate_json path), not
alktype's.
Removes the AlkTypeKind::Timestamp variant, its to_bast_str/from_bast_str
mapping, the builder's Schema::timestamp() constructor, the
validate_timestamp/is_rfc3339_timestamp validator arms, and the
materializer/reader/engine timestamp arms. Updates the meta-schema
primitive enum (14 -> 13), the spec docs (bast-format.md, schema-layer.md,
builder.md, validation.md, overview.md, data-access.md, README.md), and
the kind-count references (19 -> 18).
Verification: cargo test --release (407 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean), cargo build --target
wasm32-unknown-unknown --release (clean).
- N1: number_from_f64 now returns AlkTypeError::Access for non-finite
floats instead of silently materializing Value::Null. A NaN/Inf in the
buffer surfaces as a clear access error rather than a misleading
'expected a number' validation error.
- N3: drop the dead Value::String arm from check_bytes; the materializer
only ever emits bytes as an array of u8. Update the validation-model
docs to match.
- N4: fix stale ADR references in doc comments (ADR-096 -> ADR-002,
ADR-097 -> ADR-003, ADR-098 -> ADR-004, ADR-101 -> ADR-007).
N2 (BastType::alk_kind returning Struct for ) left as documented;
every current caller resolves the ref first, so forcing Option through
the call sites is churn without benefit.
Verification: cargo test --release (411 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean).
Resolves review #003 finding M3.
- Add bast_meta::validate_bast_doc and call it from AlkTypeEngine::compile
before parsing. Malformed annotations (unknown endian/encoding strings,
non-integer align/maxLength, missing required properties) now surface as
AlkTypeError::Schema instead of being silently tolerated by the parser.
- Align the meta-schema TypeRef with the parser: allow inline struct/union/
enum as TypeRefs (the parser and builder already accepted them; the
meta-schema and spec did not). Update bast-format.md TypeRef table and
the BastType doc comment to the seven-form vocabulary.
Verification: cargo test --release (410 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean), cargo build --target
wasm32-unknown-unknown --release (clean).
Step 9 (convert tests to BAST format) was a no-op: steps 4-8 converted
the tests as they went. The only remaining reference in
src/tests was the intentional rejection test at
src/schema.rs:462 (asserting the old keyword form is rejected). Full
suite passes: 389 tests (312 lib + 77 integration).
Step 10 (sync architecture docs and ADRs):
Descriptive docs rewritten/updated for BAST:
- schema-layer.md: rewritten for the BAST parser (BastDoc/BastDef/
BastType typed tree, AlkTypeKind enum with to_bast_str/from_bast_str,
what was removed). Points at bast-format.md for the normative format.
- validation.md: rewritten for the two-validator model
(bast_validation for validate_bytes, standard jsonschema for
validate_json). Documents the repurposed build_validator, the
AlkTypeError::Validation uniform payload (D-BAST-009), and what is
removed.
- builder.md: updated all output examples to BAST JSON
(struct_() -> { kind: struct, fields: [...] }; object() -> standard
JSON Schema). Documents build_doc, count(), and the field-name union
fields requirement (D-BAST-005).
- overview.md: updated for BAST (what/why, schema-is-the-format table,
dependencies, architecture pointers, design decisions table).
- README.md (architecture index): updated document table, ADR table
(new ADR-BAST + ADR-VAL-SPLIT, superseded ADR-001), OQ table
(OQ-007/OQ-008 resolutions updated for BAST-native validator), and
key design principles (#1, #2, #7, #10 reworded for BAST).
- data-access.md: updated tunion function signatures to BastUnion and
the variant resolution to return BastType (resolve_typeref for refs).
- layout-engine.md: updated construct signatures
(LayoutBuilder::new(bast_doc, root_name), OffsetMap::compute(&doc),
SequentialReader::new(bast_doc, root_name)), the recursive-walk
description (BAST typed tree), and composite-kind headings
(TStruct/TUnion/TArray -> struct/union/array). Added D-BAST-004
note on array count requirement.
New ADRs:
- ADR-BAST (bast-bast-format.md): the BAST format, meta-schema,
//kind vocabulary, design principles, what is removed, the
enum index bounds bug fix. Supersedes ADR-001's format-specific
content; records D-BAST-001..009.
- ADR-VAL-SPLIT (val-split-two-validator-model.md): the two-validator
model (BAST-native for validate_bytes, standard jsonschema for
validate_json), the repurposed build_validator, the uniform
AlkTypeError::Validation payload. Refines ADR-004's validation
strategy and ADR-010's validation step; records D-BAST-006/007/009.
Amended ADRs (supersession/amendment notes added; original decision
text preserved as historical record):
- ADR-001: format-specific content superseded by ADR-BAST;
purpose/scope and schema-is-the-format principle retained.
- ADR-002: unchanged under the pivot; one-line note that the input
format changed but the modes didn't.
- ADR-003: annotation semantics retained; annotation location moved
to BAST type-level properties (amended by ADR-BAST).
- ADR-004: AlkTypeError enum retained (D-BAST-009); validation
strategy section refined by ADR-VAL-SPLIT.
- ADR-009: builder API surface retained; build() output format
amended to BAST / standard JSON Schema by ADR-BAST (D-BAST-008).
- ADR-010: validate_bytes two-step concept retained; validation step
amended to the BAST-native validator by ADR-VAL-SPLIT.
Other:
- Cargo.toml description: JSON Schema with AlkType:* custom keywords
-> BAST document.
- bast-pivot.md research record: status draft -> implemented, with a
pointer to the ADRs that superseded its decisions.
- bast-implementation.md plan: status draft -> complete, with a note
that step 9 was a no-op and step 10 is this commit.
- open-questions.md: OQ-006/OQ-007/OQ-008 resolutions updated for the
BAST-native validator.
- questions/008-unionvalidator-variant-dispatch.md: added a
post-BAST-pivot note pointing to the current bast_validation
implementation; v0.1.0 resolution text preserved as historical
record.
Verification:
- cargo test --release: 389 pass (312 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo doc --no-deps: clean
- cross-reference check: every relative link in the new/updated docs
resolves (verified by script).
The bast-pivot.md research doc had grown to 1477 lines (~58KB) through
iterative editing, pushing its most actionable content (D-BAST
decisions, POC result, migration steps) past the 50KB Read tool cap.
Agents peeking at the truncated file landed in duplicated/out-of-order
sections. Decompose into three readable-sized files with distinct roles:
- docs/architecture/bast-format.md (28KB, new): the normative BAST
format spec -- meta-schema, TypeRef, examples, validation model.
Grounded in the POC and D-BAST-001..009. Stable and safe to write
now; schema-layer.md/validation.md stay describing current code and
are rewritten post-implementation (per AGENTS.md ADR-grounding rule).
- docs/plans/bast-implementation.md (31KB, new): the execution entry
point -- ordered 10-step plan with per-step goal/files/spec-ref/
verification, the public-API semver contract table up front as a
scope-creep guardrail, and the ADR-sync checklist at the end. Each
step links to the specific bast-format.md section and D-BAST anchor.
- docs/research/bast-pivot.md (28KB, trimmed): now the research record
only -- Summary, Motivation, POC scope/result, Decisions, Risks,
References. The normative format spec, what-changes tables,
validator-split details, and migration steps moved to the two new
docs; pointers added. 1155 lines removed, 216 added.
- docs/architecture/README.md: index updated to list bast-format.md
and the two in-progress pivot docs, with notes on schema-layer.md
and validation.md being rewritten when the pivot lands.
All three files are under the 50KB Read cap, so an implementing agent
gets the whole document in one call. Cross-reference anchors verified
to resolve. No code changes; cargo test --release (396 tests) green.
Verification: cargo test --release (310 crate + 86 integration, all pass).
Scope-creep guardrail for the public API during implementation. Maps
every item re-exported from src/lib.rs to a class (breaking / additive /
unchanged) with the specific change, and every ADR (001-010) to an
action (supersede / amend / unchanged) with the reason.
Net breaking: compile (signature), validate_json/is_valid_json
(contract), Schema::build/Definitions::build (output format),
build_validator (signature or removal), and the ~13 schema::* helper
re-exports. Net additive: BAST parser, BAST-native validator,
AlkTypeKind::from_str/to_str. Net unchanged: the entire layout +
data-access + materialize + tunion layer, AlkTypeError (D-BAST-009),
the Discriminator builder, AlkTypeKind variants.
Flags three small decisions deferred to their implementation steps
(validate_json JSON Schema source, build_validator fate, schema::*
re-export retention) so they don't become drive-by semver changes.
This is a living guide — it may shift slightly during implementation,
but capturing the contract now prevents public-surface drift. Doc-only.
Converts the open question into a closed decision. Rationale: consumer
ergonomics on the combined validate_json + validate_bytes path — one
uniform payload type means one match arm downstream. Option 2
(Validation(String)) would force validate_json to flatten its
structured errors to a String, losing information on the richer path to
accommodate the less rich one. The no_std/minimal-build angle that
option 2 was meant to enable is moot: validate_json requires jsonschema
regardless, so a bytes-only no_std build already has to give up
validate_json as a separate larger decision; dropping the type from one
error variant doesn't unlock it.
Updates Phase 1 step 5 to reference D-BAST-009 for the error
construction pattern, and rewrites POC Result observation 4 from
'deferred decision' to 'decided — see D-BAST-009'.
No semver-relevant change to the Validation variant. Doc-only.
Adds the POC Result section (POC on branch bast-validator-poc, commit
f371fe4 — 20/20 tests, full 416-test suite green, clippy/wasm/doc clean).
Hypothesis confirmed: a recursive walker over the BAST type tree fully
replaces the 19 custom keyword validators on the validate_bytes path,
recovers OQ-008 union variant dispatch, and fixes the enum-membership
dead constraint. The POC code is reference scaffolding on the branch
and is not merged to main — it is superseded by Phase 1 step 5.
Marks Phase 1 step 2 and the POC Scope section as done with pointers to
the result section.
Elevates the deferred AlkTypeError::Validation payload-shape question
to OQ-BAST-001: keep jsonschema::ValidationError<'static> (POC choice,
simplest, dependency stays) vs introduce Validation(String) (drops
jsonschema from the error type; semver-relevant public-API change).
Decision belongs to the production refactor.
Verification: doc-only change, no code touched.
- Rewrite Validator Split around BAST-native validator for
validate_bytes (walks BAST, checks value-domain constraints, no
external JSON Schema needed); validate_json uses standard
jsonschema::Validator from consumer-provided JSON Schema
- Add targeted POC: BAST-native validator replacing 19 custom keyword
validators on the bytes path, verified via existing test suite
- Fix meta-schema: require count on arrays (variable-element arrays
deferred per OQ-001), add optional fields array to UnionDef for
field-name discriminators
- Remove lying no-count array example, replace with deferred note
- Document dead enum constraint (materialized index never matches
string-membered enum); BAST-native validator fixes it via index
bounds check
- Resolve all 7 OQs + 3 spec gaps as D-BAST-001 through D-BAST-008
- Update Migration Path, Risks table, Engine internals to reflect
BAST-native validator
- Clarify 'no pocs needed' was an overcorrection: layout swap needs
no POC, but the validation model does
Verification: docs-only change, no code affected
- Remove the four proposed POCs: they were implementation smoke tests,
not de-risking probes. The pivot is a backend swap on a proven layout
engine; byte-identity is already proven and the layout code is
unchanged, so there is nothing empirical left to de-risk.
- Remove the recursive TreeNode example and the recursion mention in
design principle 2: recursion is not a binary-layout concern and the
engine has no cycle detection.
- Add a Spec Gaps section: validate_bytes semantics after keyword
validator removal (UnionValidator variant dispatch regression),
arrays of variable-length elements (engine rejects them), and
field-name discriminator unions (meta-schema cannot express them).
- Reframe the engine change as an accessor-layer refactor: the
walkers' (kind, field list, annotations) reads change; everything
beneath them carries over unchanged.
Correct the validator split section to clarify that jsonschema is
not removed — it remains the JSON Schema validator for both paths
(BAST meta-schema validation and standard JSON payload validation).
Only the custom keyword registration path is removed. Also fix the
build_validator and validate_json entries in the engine changes
table to reflect that they are repurposed, not removed.
Proposes replacing custom JSON Schema keywords with a standalone
kind-based JSON format (BAST) using / for composition.
Covers format design, meta-schema, engine changes, validator split,
codegen future, ABI adapter potential, migration path, 7 open
questions, and 4 proposed POCs.
- Add README.md reflecting the v0.1.0 state: 19 AlkType kinds, two
layout modes, builder + AlkTypeEngine usage example (verified to
compile and run), validation entry points, crate independence,
untrusted-schemas guarantee, docs pointers. Mirrors the alkvault
README structure.
- Add AGENTS.md with alktype-specific git workflow, project
conventions (no comments, AlkTypeError, untrusted schemas, overflow
safety, no async, no feature flags, wasm-clean, preserve_order
load-bearing, no unsafe), verification commands, and ADR/OQ index.
Blocks auto-commit on semver-relevant public API changes per the
crates.io 0.1.0 contract.
- Add LICENSE-MIT and LICENSE-APACHE (dual MIT/Apache-2.0, matching
alkvault and the Cargo.toml license field).
- Cargo.toml: add readme, keywords, categories, rust-version = "1.85".
- Fix broken intra-doc link in builder.rs: DiscriminatorKind ->
crate::schema::DiscriminatorKind (cargo doc now warning-free).
- N1 (review #002): document is_rfc3339_timestamp as non-strict in the
function doc comment. Lists the specific gaps (day-of-month per
month, seconds range, leap seconds) and points consumers needing
strict validation to chrono/time.
- N2 (review #002): document the FieldValue::Bytes-for-Record API
asymmetry in the FieldValue enum doc and on read_record_value.
- .opencode/agents/implementation-specialist.md: point to AGENTS.md
for full convention details (matches the alkvault pattern).
- review #002: mark N1/N2 resolved; all 7 findings now closed.
Verification:
- cargo test --release: 396 tests pass (310 crate + 86 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo doc --no-deps: clean (no broken intra-doc link warnings)
- cargo build --target wasm32-unknown-unknown --release: clean
- cargo publish --dry-run --allow-dirty: clean
The immediate downstream consumer (alkcall) accepts schemas from
arbitrary internet peers in its hub/spoke topology. A panic is the
wrong failure mode for a malicious or unsupported schema - an Err
the caller can handle is correct.
Three sites converted:
- offset_map.rs: _ => Err(Offset { unsupported AlkType kind for
aligned offset computation })
- layout_builder.rs: _ => Err(Offset { unsupported AlkType kind
for packed layout computation })
- materialize.rs: _ => Err(Schema { internal: union discriminator
type N is not a supported byte discriminator })
The materialize.rs site uses Schema (not Access) because a wrong
disc_type is a schema-authoring bug (parse_discriminator should have
caught it), not a buffer-access error. The sibling sites in
sequential_reader.rs and tunion.rs already returned Err(Schema) -
only materialize.rs was the holdout.
Note: the k if k.is_fixed_size() guard in offset_map and
layout_builder means the compiler cannot enforce exhaustiveness at
compile time. Converting _ from unreachable! to Err is the runtime
mitigation. A future refactor could list all fixed-size kinds
explicitly to restore compile-time checking. Deferred to a separate
cleanup pass.
Verified: no unreachable! remains in production code (the one
remaining hit at offset_map.rs:688 is inside a #[test] fn).
cargo test --release: 396 tests pass, 0 failures
cargo clippy --all-targets -- -D warnings: clean
cargo build --target wasm32-unknown-unknown --release: clean (prior)
Four of seven review findings resolved. 5 new tests (391 -> 396 crate
tests; 438 -> 443 total). cargo test, clippy, wasm32 all green.
M2 (data_access.rs): write_bytes now validates data_len fits in u32
before the length-prefix cast. A >4GiB blob returns Access error
instead of silently writing a truncated length prefix (silent data
corruption on read-back).
M1 (builder.rs): Definitions::merge_into rewritten to access self.defs
directly instead of round-tripping through self.build() with a double-
cloned/unwrap_or_default chain that could silently drop definitions
on a shape mismatch. 4 new tests: insert-when-absent, merge-into-
existing, overwrite-duplicate-keys, no-op-on-non-object-top.
L1 (materialize.rs): byte-offset discriminator arm of
materialize_union_packed now uses checked_add for offset+disc_offset
and disc_abs_offset+disc_size, returning Access error on overflow.
Mirrors the existing sequential_reader.rs::read_union_value pattern.
L3 (error.rs): AlkTypeError::source() now returns Some(inner) for the
Validation variant (jsonschema::ValidationError implements
std::error::Error). Existing source_returns_none_for_all_variants
test split into source_returns_none_for_schema_offset_access and
source_returns_some_for_validation_variant.
Deferred: L2 (unreachable! -> Err, defense-in-depth), N1 (non-strict
RFC 3339 validator, docs-only), N2 (FieldValue::Bytes for Record,
API asymmetry). Review doc updated with resolution section.
Full source read of all 13 src/*.rs files for correctness, panic
safety, and API ergonomics ahead of v0.1.0 crates.io publish.
Findings:
- M1: Definitions::merge_into silently drops data via double-
unwrap_or_default chain; untested public API
- M2: write_bytes truncates u32 length prefix on >4GiB data
(data_len as u32 without bounds check)
- L1: materialize.rs union path uses unchecked offset arithmetic
(sequential_reader.rs sibling uses checked_add)
- L2: three unreachable!() in production code (offset_map, layout_
builder, materialize)
- L3: AlkTypeError::source() returns None for Validation variant
(ValidationError implements std::error::Error)
- N1: is_rfc3339_timestamp is non-strict (Feb 31 passes, seconds
unchecked)
- N2: SequentialReader returns FieldValue::Bytes for Record (API
asymmetry vs other composites)
Verification baseline (commit c0217d9):
- cargo test --release: 438 tests pass (391 crate + 47 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo build --target wasm32-unknown-unknown --release: clean
- no unsafe, no TODO/FIXME, all unwrap/expect/panic in test modules
POC: /workspace/alktype-builder-poc/ (18/18 tests pass, findings in
docs/research/alktype-builder-poc/findings.md). Round 2 adds the SFTP
Packet validate_bytes tests (7 new: valid Init/Read/Write/Status,
short buffer, unknown discriminator, over-maxLength Bytes).
Open questions resolved (OQ-004 through OQ-008):
- OQ-004: Discriminator::Field name is String (already implemented;
docs updated to mark resolved)
- OQ-005: Both union discriminator kinds return the same shape:
{__discriminator, ...variant-fields}. Field-name path also had a
real offset bug (returned start, not end) - fixed.
- OQ-006: builder.md Example 3 now wraps the Union in a
Schema::struct_().field("payload", ...) and merges $defs via
Definitions::merge_into (matches the engine's AlkType:Struct-at-root
constraint and the SFTP wire shape)
- OQ-007: Bytes materialization is array-of-u8 (Value::Array of
Value::Number, one entry per byte 0..=255). BytesValidator accepts
both Value::String (validate_json) and Value::Array (validate_bytes).
maxLength = max byte count. Replaces the lossy from_utf8_lossy path
that corrupted non-UTF-8 bytes and broke maxLength semantics.
- OQ-008 (new): UnionValidator now dispatches to variant schemas via
sub-validators built at factory time. AlkTypeEngine::compile calls
schema::inline_union_variant_refs before build_validator to inline
$refs in union mapping entries (necessary because union_factory
receives the union node, but $defs live at the schema root).
Production-readiness fixes in src/ (no stubs/hedges in a published crate):
- materialize.rs: Record stub -> full count-prefixed key/value pair
implementation per schema-layer.md TRecord
- materialize.rs: root_of() was broken (returned the current node, not
the schema root) -> root schema threaded through every recursive call
so resolve_ref_or_inline can resolve $refs for nested composites
- builder.rs: LengthPrefixed encoding setter was a no-op when the
keyword was already in object form -> complete the branch (updates
the encoding entry in place for both LengthPrefixed and OffsetIndirect)
- builder.rs, engine.rs: POC-referencing comments cleaned up; the
round-trip test's or_else fallback (papering over write_field being
aligned-only) replaced with direct byte writes
Documentation:
- builder.md: Example 3 updated; Discriminator::Field spec shows String;
Open Questions section updated (OQ-004 resolved)
- validation.md: AlkType:Bytes and AlkType:Union validator descriptions
updated for array-of-u8 form and variant dispatch
- open-questions.md: OQ-004/005/006/007/008 marked resolved; new
Validation theme entries
- questions/004-008: individual OQ files updated with resolutions
- findings.md: round 2 results documented
Verification:
- cargo test: 369 -> 391 tests pass (22 new: 14 materialize, 5
inline_union_variant_refs, 3 validation/builder)
- cargo clippy --all-targets: clean
- POC: 11 -> 18 tests (7 new SFTP Packet tests); all pass
Four open questions were scattered inline in builder.md and the POC
findings doc. Moved them into the central OQ tracker under
docs/architecture/questions/ and updated the index:
- OQ-004: Discriminator::Field name type (&str vs String) — raised in
builder.md during ADR-009 spec drafting
- OQ-005: Union materialization shape (byte-offset vs field-name
consistency) — raised in the POC findings
- OQ-006: Builder spec Example 3 wrap Union in Struct — raised in the
POC findings (doc fix; engine requires AlkType:Struct at top level)
- OQ-007: Bytes materialization lossy UTF-8 — raised in the POC
findings (blocks SFTP use case for validate_bytes)
Index updates:
- open-questions.md: new 'Schema Construction' and 'Validation' theme
groups; new 'Open' section for active investigation targets (distinct
from 'Deferred / Blocked' which holds scope-parked OQs)
- README.md: OQ table extended with OQ-004 through OQ-007
- builder.md: inline OQ-004 replaced with a tracker reference
- findings.md: inline OQ-005/006/007 replaced with tracker references
Doc-only change; 369 tests pass.
POC: /workspace/alktype-builder-poc/ (11/11 tests pass, findings in
docs/research/alktype-builder-poc/findings.md). The POC code lives outside
the repo per the internal dev convention.
Implementation:
- src/builder.rs: Schema, Definitions, Discriminator types. Constructors
for all 19 AlkType kinds + standard JSON Schema types (object/array/
string/integer/number/boolean/null/any). Setters for ADR-003 annotations
(endian/align/encoding/max_length), composite builders (field/required/
items/mapping), and standard JSON Schema constraints (minimum/maximum/
minLength/minItems/maxItems/format/title/description). 16 unit tests.
- src/materialize.rs: materialize_packed and materialize_aligned functions
that walk a schema + buffer to produce a serde_json::Value tree. Recurses
into Struct, Array, Union (byte-offset discriminator). Record is stubbed
(deferred for the POC scope).
- src/engine.rs: AlkTypeEngine::validate_bytes(&[u8]) added (ADR-010).
Dispatches on layout mode, materializes Value, then validates against
the existing jsonschema validator. 7 unit tests.
- src/lib.rs: pub mod builder, pub mod materialize; re-exports Schema,
Definitions, Discriminator.
Verification:
- cargo test: 346 -> 369 tests pass (23 new: 16 builder, 7 validate_bytes)
- cargo clippy --all-targets -- -D warnings: clean
- POC (11 tests): builder round-trip + validate_bytes (packed + aligned) +
validate_json for call payloads; all pass
Findings:
- Top-level schema must be AlkType:Struct (existing constraint); unions are
field types within a struct. Builder spec Example 3 needs a doc fix.
- Builder field order preserved (preserve_order feature, load-bearing for
packed mode).
- validate_bytes correctly distinguishes Access (read phase) from
Validation (validate phase) errors, with field paths.
- validate_json path unchanged for call payloads.
Open questions surfaced (OQ-005, OQ-006, OQ-007) tracked in findings.md;
to resolve before the SFTP Packet POC round.
Rename all 19 JSON Schema custom keyword strings from "TypeDef:*"
to "AlkType:*" (e.g., "TypeDef:Struct" -> "AlkType:Struct")
across source, tests, and docs. This is a breaking change to the
schema format itself — existing schemas using the old keywords
must be updated.
Rename the Rust identifiers:
- TypedefEngine -> AlkTypeEngine
- TypedefError -> AlkTypeError
- TypeDefKind -> AlkTypeKind
- TYPEDEF_PREFIX -> ALKTYPE_PREFIX
- get_typedef_kind{,_loose,_loose_enum,_enum} ->
get_alktype_kind{,_loose,_loose_enum,_enum}
Update error message strings ("unknown TypeDef kind" ->
"unknown AlkType kind"), 11 test function names containing
typedef_kind/to_typedef_error, and doc-comment prose ("TypeDef
kind" -> "AlkType kind", "typedef engine" -> "alktype
engine", "typedef schema" -> "alktype schema"). Fix the broken
docs/architecture/crates/typedef/ path references in source doc
comments to point at docs/architecture/ directly. Rebrand the
typedef:annotation test fixture and the "not-a-typedef" test
string to their alktype equivalents.
Update ~20 generic "typedef" prose references in the architecture
docs ("typedef is the binary struct engine", "use typedef",
"typedef limitation", "replaced by typedef", etc.) to alktype.
Rename TypedefEngine in the ADR-007 code example to AlkTypeEngine.
Preserve as provenance per the prior prose-rebrand decision:
typedef.ts references (external TypeBox source file),
docs/research/alknet-typedef/findings.md research citations,
/workspace/alknet-typedef-poc/ POC path, and the
"alknet-typedef:" research section headers in findings.
Build, 295 tests, and clippy all pass clean.
Renumber ADRs 095-102 to 001-008 and OQs 069-071 to 001-003, and
update all cross-references (titles, body prose, file-path links,
tables) across the 5 spec docs, README, open-questions index, and
all 11 ADR/OQ files. Inline the ADR-009 door-type definition from
the parent alknet project (broken cross-project reference).
Rebrand prose: alknet-typedef -> alktype in headings, body text,
dependency diagrams, and "additions" notes. Disambiguate the prior
failed attempt at /workspace/@alkimiadev/alktype/ as "the
@alkimiadev/alktype prototype" to distinguish it from this crate.
Historical research citations (docs/research/*, /workspace/alknet-typedef-poc/)
are kept as-is for provenance.
Rename the crate in Cargo.toml ([package].name, [lib].name) and
update the 11 use alknet_typedef::* imports across the 4 test files.
Rebrand the crate-level doc comment in src/lib.rs.
The TypeDef:* keyword strings, TypedefError/TypedefEngine identifiers,
and other code-level references are unchanged — those are a separate
code rebrand pass.
Build, 295 tests, and clippy all pass clean.
Copy the binary struct engine (src/, tests/) verbatim from
alknet/crates/alknet-typedef and create a standalone Cargo.toml
(workspace-inherited fields inlined). Port the architecture docs
(specs, ADRs 095-102, OQs 069-071) from alknet's nested multi-crate
layout to a flat single-crate layout, fixing relative link paths.
Build, 295 tests, and clippy all pass clean.