Skip to content

Commit 4436e1e

Browse files
docs(spec): document wire-level carrier sections (proposals 0001 §4 + 0002 §5) (#108)
## Summary Closes the remaining doc gate for both proposals: - Proposal 0001 §Acceptance criterion 4 (spec section in \`spec/type-safety-levels-for-wasm.adoc\`) - Proposal 0002 §Gate 5 (same) Adds a new top-level **\"Wire-Level Carrier Sections\"** section after \"Implementation Architecture\" with all 4 carriers documented: \`typedwasm.ownership\` (existing), \`typedwasm.regions\` + \`typedwasm.capabilities\` (proposal 0001 \`[review]\`), \`typedwasm.access-sites\` (proposal 0002 \`[review]\`). ## Content Per-section: full wire format (verbatim from the proposals), Idris2 spec-type mappings, codec entry points (\`parse_*\` / \`build_*\` in \`crates/typed-wasm-verify/src/section.rs\`). Plus overview table, versioning policy, and cross-references to #106 roadmap + #94 / #95 / #96 / #97 open questions. Net +187 lines of doc, no code change. ## What this leaves for [accepted] Per proposal 0001 §Acceptance criteria: - [x] Criterion 1 — paired producer sign-off (4 review comments on #165 + #402) - [ ] Criterion 2 — wire format mapped against \`Region.idr\` / \`Pointer.idr\` / \`ResourceCapabilities.idr\` (could land as proof-side review) - [x] Criterion 3 — codec landed (regions via #77, capabilities + access-sites via #107) - [x] Criterion 4 — **this PR** - [x] Criterion 5 — cross-repo producer issues filed (ephapax#221, affinescript#444 shipped) Per proposal 0002 §Acceptance criteria: - [x] Gate 1 — ADR-021 v2 emit-flip protocol drafted - [x] Gate 2 — paired producer review (ephapax#222 + affinescript#449) - [x] Gate 3 — codec landed (via #107) - [ ] Gate 4 — \`verify_access_sites\` verifier pass (needs cross-section MissingDependentCarrier check) - [x] Gate 5 — **this PR** - [ ] Gate 6 — cross-repo producer codegen issues Remaining for both proposals to flip to \`[accepted]\`: the verifier-pass plumbing (\`verify_capabilities\` for 0001, \`verify_access_sites\` for 0002 — both cross-section). Tracked under #106. ## Test plan - [x] AsciiDoc renders cleanly (table columns balanced, code blocks valid) - [x] No path/file references break (verified against current section.rs API names) - [x] No collisions with existing spec doc sections - [x] Doc-only change — no code, no CI implications beyond aspect tests (which now pass after #105 root-cause fix) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent db379c9 commit 4436e1e

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

spec/type-safety-levels-for-wasm.adoc

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,3 +686,190 @@ does NOT address:
686686
| Rust
687687
| Makes typed-wasm a TypedQLiser target language
688688
|===
689+
690+
== Wire-Level Carrier Sections
691+
692+
The post-codegen verifier (`crates/typed-wasm-verify/`) re-enforces a
693+
subset of these levels on emitted wasm bytes. To do that without
694+
re-parsing source, producers emit small custom sections — wasm's
695+
extension mechanism — that carry just enough of the source-level type
696+
information for the verifier to reconstruct the relevant judgments.
697+
698+
All carrier sections are independently optional from the verifier's
699+
perspective: absence is "no claim made," not "claim of compliance." A
700+
producer that emits only `typedwasm.ownership` is conforming with
701+
respect to L7 / L10 but makes no L2-L6 / L15 claims that the verifier
702+
can re-check.
703+
704+
[cols="1,1,1,2"]
705+
|===
706+
| Section | Covers | Status (2026-05) | Codec entry point
707+
708+
| `typedwasm.ownership`
709+
| L7 aliasing, L10 linearity
710+
| Shipped; canonical wire format
711+
| `parse_ownership_section_payload` / `build_ownership_section_payload`
712+
713+
| `typedwasm.regions`
714+
| L2 region-binding, L3 type-compatible access, L4 null-safety, L5 bounds-proof, L6 result-type
715+
| Proposal 0001 `[review]`; codec behind `unstable-l2`
716+
| `parse_regions_section_payload` / `build_regions_section_payload`
717+
718+
| `typedwasm.capabilities`
719+
| L15 resource capabilities (lattice + per-function requirements; excluding L15-C per-call-site grants)
720+
| Proposal 0001 `[review]`; codec behind `unstable-l15`
721+
| `parse_capabilities_section_payload` / `build_capabilities_section_payload`
722+
723+
| `typedwasm.access-sites`
724+
| L2 enforcement on emitted bytes (per-instruction `(func_idx, byte_offset) -> (region_id, field_id)` mapping)
725+
| Proposal 0002 `[review]`; codec behind `unstable-l2`
726+
| `parse_access_sites_section_payload` / `build_access_sites_section_payload`
727+
|===
728+
729+
=== `typedwasm.ownership` (L7 / L10)
730+
731+
Wire format (little-endian, byte-aligned):
732+
733+
----
734+
u32le count
735+
for each entry:
736+
u32le func_idx
737+
u8 n_params
738+
u8[] param_kinds (0=Unrestricted, 1=Linear, 2=SharedBorrow, 3=ExclBorrow)
739+
u8 ret_kind
740+
----
741+
742+
The verifier reconstructs each function's ownership-annotated signature
743+
and checks the body against it for L7 (no aliasing of `ExclBorrow`) and
744+
L10 (`Linear` consumed exactly once on every code path). The 4-kind
745+
enum (`OwnershipKind`) is fixed at v1.
746+
747+
Lenient on truncation: a short read yields zeros for the missing
748+
bytes (interpreted as `Unrestricted` kinds and `func_idx = 0`). This
749+
defence-in-depth choice preserves cross-implementation parity with
750+
the OCaml reference parser in AffineScript's `lib/tw_verify.ml`.
751+
752+
=== `typedwasm.regions` (L2-L6)
753+
754+
Wire format (little-endian, byte-aligned, lenient on truncation):
755+
756+
----
757+
u16le version (= REGIONS_SECTION_VERSION = 1)
758+
u32le region_count
759+
for each region (in index order, 0..region_count-1):
760+
u32le name_len
761+
u8[] name (UTF-8, no NUL terminator)
762+
u32le field_count
763+
for each field (in declaration order):
764+
u32le field_name_len
765+
u8[] field_name (UTF-8)
766+
u8 kind (0=Scalar, 1=PtrOwning, 2=PtrBorrow, 3=PtrExclusive)
767+
u8 wasm_ty (0..10 = U8..WBool, 0xFF = N/A for ptr kinds)
768+
u32le target_region (0xFFFFFFFF if Scalar; else index into region table)
769+
u8 nullability (0=NonNull, 1=Nullable)
770+
u32le cardinality (1=single, n>1=fixed array, 0=unbounded/dynamic)
771+
u32le region_byte_size (sum-check; verifier may cross-check)
772+
----
773+
774+
Maps to Idris2 spec types: `region_count` + region table → `List Region`
775+
(lookup-by-index); `field_count` + field table → `Schema` per region;
776+
`wasm_ty` → `WasmType` (11 variants); `kind` → `PtrKind` extended with
777+
`Scalar`; `nullability` → `Nullability`; `cardinality` feeds
778+
`InBounds idx count` for L5; `target_region` is the foreign-key for L2.
779+
780+
v0 producer surface narrowness — across AffineScript + ephapax, no
781+
producer emits v128/SIMD / 128-bit ints / reference types /
782+
variable-cardinality fields. See proposal 0001 §"v0 producer surface"
783+
for the full reserved-codepoint enumeration.
784+
785+
=== `typedwasm.capabilities` (L15)
786+
787+
Wire format (little-endian, byte-aligned):
788+
789+
----
790+
u16le version (= CAPABILITIES_SECTION_VERSION = 1)
791+
u32le capability_count
792+
for each capability (in index order, 0..capability_count-1):
793+
u32le name_len
794+
u8[] name (UTF-8)
795+
u32le function_count
796+
for each function:
797+
u32le func_idx (index into wasm function section)
798+
u32le required_count
799+
u32le[required_count] required_capability_indices
800+
(indices into capability table above;
801+
MUST be strictly increasing → trivially
802+
encodes ResourceCapabilities.DistinctCaps)
803+
----
804+
805+
Maps to `ResourceCapabilities.idr`: the flat capability table is
806+
`CapabilitySet`; strictly-increasing `required_capability_indices`
807+
structurally encodes `DistinctCaps`; per-function indices are the raw
808+
data the verifier feeds to `ContainedIn`.
809+
810+
L15-C (call-graph monotonicity / `CallCompatible`) is deferred to a
811+
v1.4.x follow-up section `typedwasm.capability-grants` — see
812+
typed-wasm#96.
813+
814+
The codec's parser defensively sorts and dedups `required` indices on
815+
read, so a future producer that regresses on the producer obligation
816+
still yields a `DistinctCaps`-conforming structure to downstream
817+
verification.
818+
819+
=== `typedwasm.access-sites` (L2 enforcement)
820+
821+
Wire format (little-endian fixed-width header, LEB128 per entry):
822+
823+
----
824+
u16le version (= ACCESS_SITES_SECTION_VERSION = 1)
825+
u32_leb128 entry_count
826+
for each entry (in producer-emission order):
827+
u32_leb128 func_idx
828+
u32_leb128 instruction_byte_offset (within function body, post-codegen,
829+
post any wasm-opt rewrite)
830+
u32_leb128 region_id (index into typedwasm.regions table)
831+
u32_leb128 field_id (index into target region's field table)
832+
----
833+
834+
Encoding B (LEB128 per field) per proposal 0002 measurement: ~5
835+
B/entry on average, ~1.1% module overhead at the 6-fixture spec scale.
836+
Encoding A (flat 4× `u32le`, 16 B/entry, ~2.9% overhead) was rejected;
837+
encoding C (per-function batched delta offsets, ~3.5 B/entry,
838+
substantially more complex codec) is deferred to a possible v2.
839+
840+
Entries are NOT required to be sorted in v1; a future v2 MAY require
841+
sorted-by-`(func_idx, offset)` to enable binary search. v1 consumers
842+
build a `HashMap<(u32, u32), (u32, u32)>` at parse time.
843+
844+
The verifier resolves a typed access by looking up the entry in the
845+
access-sites map, then cross-referencing `region_id` + `field_id`
846+
against `typedwasm.regions`. Without this section, L2-the-enforcement
847+
(every typed access resolves to a declared region/field) cannot fire
848+
on emitted bytes even though L2-the-spec (regions/fields exist;
849+
cross-module schema agreement) is fully covered by
850+
`typedwasm.regions`.
851+
852+
`MissingDependentCarrier` semantics: emitting `typedwasm.access-sites`
853+
without `typedwasm.regions` is a hard error — the access-sites entries
854+
reference region/field indices that no companion table resolves.
855+
856+
=== Versioning policy
857+
858+
`typedwasm.ownership` is unversioned (the original carrier; renamed
859+
from `affinescript.ownership` in PR #65). The three carriers added by
860+
proposals 0001 + 0002 all begin with `u16le version`. Forward-compat
861+
contract:
862+
863+
* *Additive* changes (new trailing fields per record) ship as the same
864+
major version; the lenient reader silently ignores the new bytes on a
865+
v1 consumer.
866+
* *Breaking* changes bump the version and the verifier rejects unknown
867+
versions with a fresh `*SectionError`-style variant (per-section).
868+
869+
=== Cross-references
870+
871+
* Codec: `crates/typed-wasm-verify/src/section.rs`
872+
* Proposal 0001 (regions + capabilities): `docs/proposals/0001-multi-producer-carrier-section.adoc`
873+
* Proposal 0002 (access-sites): `docs/proposals/0002-access-site-carrier.adoc`
874+
* Pre-stage roadmap: typed-wasm#106 (`[review]` → `[accepted]` tracker)
875+
* Open questions: typed-wasm#94 (WBool wire width), #95 (region-imports v0.3), #96 (capability-grants L15-C), #97 (producer-readiness checklist)

0 commit comments

Comments
 (0)