Skip to content

Commit 8006c35

Browse files
Merge pull request #28 from hyperpolymath/reinforce/vclut-25-phase5b-wire
vcl-ut #25 P5b (step 1): C-ABI wire codec for Statement
2 parents 2d9e3cd + 998b484 commit 8006c35

4 files changed

Lines changed: 1297 additions & 0 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
= VCL-total Statement Wire Format v1 (P5b, vcl-ut#25)
2+
:toc:
3+
4+
// SPDX-License-Identifier: PMPL-1.0-or-later
5+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
6+
7+
The C-ABI marshalling format for a parsed `Statement`. The Rust
8+
encoder/decoder (`wire.rs`) is normative for v1; the P5b-step-2 Idris2
9+
decoder MUST decode this identical byte stream into `Grammar.idr`'s
10+
`Statement` (the certified type), proven total. Cross-language
11+
conformance is by shared golden vectors.
12+
13+
Design: a deterministic, versioned, length-prefixed binary TLV.
14+
*Rationale:* no parser dependency / no extra trust surface (vs CBOR),
15+
fully auditable, byte-exact, total decode. This is the boundary the
16+
hypatia<->verisim seam crosses; it is specified, not improvised.
17+
18+
== Conventions
19+
20+
* All multi-byte integers are *little-endian, fixed width*.
21+
* `u8` = 1 byte; `u32` = 4 bytes; `u64`/`i64` = 8 bytes (`i64`
22+
two's-complement); `f64` = 8 bytes IEEE-754 (`f64::to_bits`,
23+
bit-exact — NaN/inf preserved).
24+
* `bool` = 1 byte, `0x00` false / `0x01` true (any other byte =>
25+
`WireError::BadBool`).
26+
* `string` = `u32` byte length, then that many UTF-8 bytes.
27+
* `option<T>` = `0x00` (none) | `0x01` (some) then `T`.
28+
* `list<T>` = `u32` count, then that many `T`.
29+
* Every sum type = a `u8` *discriminant* then its payload in the field
30+
order below. Unknown discriminant => `WireError::BadTag`.
31+
32+
== Header
33+
34+
`56 43 4C 57` ("VCLW") then `u16` version. v1 = `01 00`.
35+
Bad magic/version => `WireError::BadMagic` / `BadVersion`.
36+
37+
== Discriminants (STABLE — never renumber; append only)
38+
39+
Where `Grammar.idr` already defines an integer mapping it is reused so
40+
the Idris decoder is a direct match:
41+
42+
* `Modality` — `modalityToInt`: Graph 0, Vector 1, Tensor 2,
43+
Semantic 3, Document 4, Temporal 5, Provenance 6, Spatial 7.
44+
* `Agent` — `agentToInt` tag: Engine 0, Prover 1 `+string`,
45+
Validator 2, User 3 `+string`, Federation 4.
46+
* `EpistemicOp` — `epistemicOpToInt`: Knows 0, Believes 1,
47+
CommonKnowledge 2.
48+
* `EpistemicRequirement` — `epReqToInt`: Knows 0 `agent,expr`,
49+
Believes 1 `agent,expr`, Common 2 `expr`, Entails 3
50+
`agent,agent,expr`.
51+
* `SafetyLevel` — `safetyLevelToInt` (Types.idr): ParseSafe 0 …
52+
EpistemicSafe 10.
53+
54+
Defined here for v1 (no Idris int map existed; now normative):
55+
56+
* `Literal`: Str 0 `string`, Int 1 `i64`, Float 2 `f64`, Bool 3
57+
`bool`, Null 4, Vector 5 `list<f64>`.
58+
* `CompOp`: Eq 0, NotEq 1, Lt 2, Gt 3, LtEq 4, GtEq 5, Like 6, In 7.
59+
* `LogicOp`: And 0, Or 1, Not 2.
60+
* `AggFunc`: Count 0, Sum 1, Avg 2, Min 3, Max 4.
61+
* `Source`: Octad 0 `string`, Federation 1 `string`, Store 2 `string`.
62+
* `ProofClause`: Attached 0, Witness 1 `string`, Assert 2 `expr`.
63+
* `EffectDecl`: Read 0, Write 1, ReadWrite 2, Consume 3.
64+
* `VersionConstraint`: Latest 0, AtLeast 1 `u64`, Exact 2 `u64`,
65+
Range 3 `u64,u64`.
66+
* `LinearAnnotation`: Unlimited 0, UseOnce 1, Bounded 2 `u64`.
67+
* `SelectItem`: Field 0 `fieldref`, Modality 1 `modality`,
68+
Aggregate 2 `aggfunc,expr`, Star 3.
69+
* `Expr`: Field 0 `fieldref`, Literal 1 `literal`, Compare 2
70+
`compop,expr,expr`, Logic 3 `logicop,expr,option<expr>`,
71+
Aggregate 4 `aggfunc,expr`, Param 5 `string`, Star 6,
72+
Subquery 7 `statement`, Epistemic 8 `epistemicop,agent,expr`,
73+
Announce 9 `agent,expr,expr`.
74+
75+
Composite: `FieldRef` = `modality` then `string` (field name).
76+
`EpistemicClause` = `list<agent>` then `list<EpistemicRequirement>`.
77+
78+
== Statement (field order = the `Grammar.idr` record)
79+
80+
`list<SelectItem>`, `Source`, `option<Expr>` (where),
81+
`list<FieldRef>` (group by), `option<Expr>` (having),
82+
`list<(FieldRef, bool)>` (order by; bool = ascending),
83+
`option<u64>` (limit), `option<u64>` (offset),
84+
`option<ProofClause>`, `option<EffectDecl>`,
85+
`option<VersionConstraint>`, `option<LinearAnnotation>`,
86+
`option<EpistemicClause>`, `SafetyLevel` (requested level).
87+
88+
After the final field the stream MUST be exhausted; trailing bytes =>
89+
`WireError::TrailingBytes`.
90+
91+
== Totality contract
92+
93+
The decoder is *total*: bounds-checked, no panics, every malformed
94+
input a typed `WireError` (same SPARK-grade lint posture as the
95+
parser). `from_wire(to_wire(s)) == s` for every `Statement` (the
96+
round-trip proptest is the machine witness; bit-exact float preservation
97+
is pinned by a golden vector). `to_wire` is deterministic (canonical:
98+
exactly one byte string per value).

src/interface/parse/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
pub mod ast;
3838
pub mod lexer;
3939
pub mod parser;
40+
pub mod wire;
4041

4142
pub use ast::Statement;
4243
pub use parser::{parse, ParseError};
44+
pub use wire::{from_wire, to_wire, WireError};
4345

4446
#[cfg(test)]
4547
mod tests {

0 commit comments

Comments
 (0)