Skip to content

Commit 998b484

Browse files
hyperpolymathclaude
andcommitted
vcl-ut #25 P5b (step 1): C-ABI wire codec for Statement
The marshalling layer between the trusted Rust parser (P5a) and the Idris2 certifier. Adds, in the standalone vcltotal-parse crate: - WIRE-FORMAT.adoc: normative spec for a deterministic, versioned, length-prefixed binary TLV (magic "VCLW" + u16 ver). Discriminants reuse Grammar.idr's *ToInt maps where they exist (modality/agent/ epReq/epistemicOp) and Types.idr safetyLevelToInt, so the P5b-step-2 Idris2 decoder is a direct match. Chosen over CBOR: no dependency / no extra trust surface, fully auditable, byte-exact. - src/wire.rs: to_wire / from_wire for the full Statement AST. Same SPARK-grade posture as the parser (crate lint-set): to_wire total + deterministic (canonical one-byte-string-per-value); from_wire TOTAL — bounds-checked cursor, no panic, no unbounded pre-allocation from untrusted counts (anti-OOM), every malformed input a typed WireError. Machine witnesses (tests/wire.rs, proptest 2048 cases): - roundtrip: from_wire(to_wire(s)) == s over arbitrary Statements (bijection on the Rust AST mirror). - decoder_total_on_garbage / _with_valid_header: arbitrary bytes never panic — Ok/Err only (trusted-boundary totality contract). - encoder_deterministic; golden vectors incl. bit-exact non-finite float preservation (inf structural, NaN by bits). Verified standalone, CI-faithful (--manifest-path --locked): clippy --all-targets -D warnings clean (SPARK lint set), 17/17 tests (6 lib + 5 parse + 6 wire), fmt clean. Step 2 (separate): the total Idris2 decoder of this identical format into Grammar.idr's certified Statement, %default total, no proof escape, with shared golden vectors for cross-language conformance — gated by the proof-corpus CI (now healthy post-#24). Stacked on the P5a branch. Refs #25. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 598df32 commit 998b484

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)