|
| 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). |
0 commit comments