Squashed 'vendor/ruvector/' content from commit b64c2172

git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
commit d803bfe2b1
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
[package]
name = "rvf-types"
version = "0.2.0"
edition = "2021"
description = "RuVector Format core types -- segment headers, enums, flags"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ruvnet/ruvector"
homepage = "https://github.com/ruvnet/ruvector"
readme = "README.md"
categories = ["data-structures", "no-std"]
keywords = ["vector", "database", "binary-format", "simd"]
rust-version = "1.87"
[features]
default = []
alloc = []
std = ["alloc"]
serde = ["dep:serde"]
ed25519 = ["dep:ed25519-dalek", "dep:rand_core"]
[dependencies]
serde = { version = "1", default-features = false, features = ["derive"], optional = true }
ed25519-dalek = { version = "2", default-features = false, features = ["alloc", "rand_core"], optional = true }
rand_core = { version = "0.6", default-features = false, optional = true }

View File

@@ -0,0 +1,278 @@
# rvf-types
Core type definitions for the RuVector Format (RVF) binary container.
## Overview
`rvf-types` defines the foundational types shared across all RVF crates:
- **Segment headers** -- magic bytes, version, flags, checksums
- **Enums** -- element types (`F32`, `F16`, `U8`, `Binary`), compression modes, distance metrics
- **Flags** -- segment-level feature flags (SIMD hints, encryption, quantization tier)
## Features
- `std` -- enable `std` support (disabled by default for `no_std` compatibility)
- `serde` -- derive `Serialize`/`Deserialize` on all public types
## Usage
```toml
[dependencies]
rvf-types = "0.1"
```
```rust
use rvf_types::{SegmentHeader, ElementType, DistanceMetric};
```
## Lineage / Derivation Types
`rvf-types` defines the types that power DNA-style lineage provenance for RVF files.
### `DerivationType` Enum
Describes how a child file was produced from its parent (`#[repr(u8)]`):
| Variant | Value | Meaning |
|---------|-------|---------|
| `Clone` | 0 | Exact copy of the parent |
| `Filter` | 1 | Subset of parent data |
| `Merge` | 2 | Multiple parents merged |
| `Quantize` | 3 | Re-quantized from parent |
| `Reindex` | 4 | HNSW rebuild or similar |
| `Transform` | 5 | Arbitrary transformation |
| `Snapshot` | 6 | Point-in-time snapshot |
| `UserDefined` | 0xFF | Application-specific derivation |
### `FileIdentity` Struct (68 bytes, `repr(C)`)
Embedded in the Level0Root reserved area at offset `0xF00`. Old readers that ignore the reserved area see zeros and continue working.
| Offset | Size | Field |
|--------|------|-------|
| `0x00` | 16 | `file_id` -- UUID-style unique identifier |
| `0x10` | 16 | `parent_id` -- parent file identifier (zeros for root) |
| `0x20` | 32 | `parent_hash` -- SHAKE-256-256 of parent manifest (zeros for root) |
| `0x40` | 4 | `lineage_depth` -- 0 for root, incremented per derivation |
```rust
use rvf_types::FileIdentity;
let root = FileIdentity::new_root([0x42u8; 16]);
assert!(root.is_root());
assert_eq!(root.lineage_depth, 0);
```
### `LineageRecord` Struct (128 bytes)
A fixed-size record for witness chain entries carrying full derivation metadata:
| Offset | Size | Field |
|--------|------|-------|
| `0x00` | 16 | `file_id` |
| `0x10` | 16 | `parent_id` |
| `0x20` | 32 | `parent_hash` |
| `0x40` | 1 | `derivation_type` |
| `0x41` | 3 | padding |
| `0x44` | 4 | `mutation_count` |
| `0x48` | 8 | `timestamp_ns` |
| `0x50` | 1 | `description_len` |
| `0x51` | 47 | `description` (UTF-8, max 47 bytes) |
### Lineage Witness Type Constants
These extend the witness chain event types for derivation tracking:
| Constant | Value | Purpose |
|----------|-------|---------|
| `WITNESS_DERIVATION` | `0x09` | File derivation event |
| `WITNESS_LINEAGE_MERGE` | `0x0A` | Multi-parent merge |
| `WITNESS_LINEAGE_SNAPSHOT` | `0x0B` | Snapshot event |
| `WITNESS_LINEAGE_TRANSFORM` | `0x0C` | Transform event |
| `WITNESS_LINEAGE_VERIFY` | `0x0D` | Lineage verification |
### `HAS_LINEAGE` Segment Flag
Bit 11 (`0x0800`) of the segment flags bitfield. Set when a file carries DNA-style lineage provenance metadata:
```rust
use rvf_types::SegmentFlags;
let flags = SegmentFlags::empty().with(SegmentFlags::HAS_LINEAGE);
assert!(flags.contains(SegmentFlags::HAS_LINEAGE));
assert_eq!(flags.bits(), 0x0800);
```
### Lineage Error Codes (Category `0x06`)
| Code | Name | Description |
|------|------|-------------|
| `0x0600` | `ParentNotFound` | Referenced parent file not found |
| `0x0601` | `ParentHashMismatch` | Parent hash does not match recorded `parent_hash` |
| `0x0602` | `LineageBroken` | Lineage chain has a missing link |
| `0x0603` | `LineageCyclic` | Lineage chain contains a cycle |
## Computational Container Types
`rvf-types` defines the segment types and header structures for the RVF computational container model, which allows `.rvf` files to carry executable compute alongside vector data.
### Segment Types
Two segment type discriminants support the computational container:
| Variant | Value | Description |
|---------|-------|-------------|
| `SegmentType::Kernel` | `0x0E` | Embedded kernel / unikernel image for self-booting |
| `SegmentType::Ebpf` | `0x0F` | Embedded eBPF program for kernel fast path |
These are defined in `segment_type.rs` and round-trip through `TryFrom<u8>`:
```rust
use rvf_types::SegmentType;
assert_eq!(SegmentType::Kernel as u8, 0x0E);
assert_eq!(SegmentType::Ebpf as u8, 0x0F);
assert_eq!(SegmentType::try_from(0x0E), Ok(SegmentType::Kernel));
```
### `KernelHeader` (128 bytes, `repr(C)`)
Describes an embedded unikernel or micro-Linux image within a KERNEL_SEG payload. Follows the standard 64-byte `SegmentHeader`.
| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| `0x00` | 4 | `kernel_magic` | Magic: `0x52564B4E` ("RVKN") |
| `0x04` | 2 | `header_version` | KernelHeader format version (currently 1) |
| `0x06` | 1 | `arch` | Target architecture (`KernelArch` enum) |
| `0x07` | 1 | `kernel_type` | Kernel type (`KernelType` enum) |
| `0x08` | 4 | `kernel_flags` | Bitfield flags (`KernelFlags`) |
| `0x0C` | 4 | `min_memory_mb` | Minimum RAM required (MiB) |
| `0x10` | 8 | `entry_point` | Virtual address of kernel entry point |
| `0x18` | 8 | `image_size` | Uncompressed kernel image size (bytes) |
| `0x20` | 8 | `compressed_size` | Compressed kernel image size (bytes) |
| `0x28` | 1 | `compression` | Compression algorithm (same as SegmentHeader) |
| `0x29` | 1 | `api_transport` | API transport (`ApiTransport` enum) |
| `0x2A` | 2 | `api_port` | Default API port (network byte order) |
| `0x2C` | 4 | `api_version` | Supported RVF query API version |
| `0x30` | 32 | `image_hash` | SHAKE-256-256 of uncompressed kernel image |
| `0x50` | 16 | `build_id` | Unique build identifier (UUID v7) |
| `0x60` | 8 | `build_timestamp` | Build time (nanosecond UNIX timestamp) |
| `0x68` | 4 | `vcpu_count` | Recommended vCPU count (0 = single) |
| `0x6C` | 4 | `reserved_0` | Reserved (must be zero) |
| `0x70` | 8 | `cmdline_offset` | Offset to kernel command line within payload |
| `0x78` | 4 | `cmdline_length` | Length of kernel command line (bytes) |
| `0x7C` | 4 | `reserved_1` | Reserved (must be zero) |
### `EbpfHeader` (64 bytes, `repr(C)`)
Describes an embedded eBPF program within an EBPF_SEG payload.
| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| `0x00` | 4 | `ebpf_magic` | Magic: `0x52564250` ("RVBP") |
| `0x04` | 2 | `header_version` | EbpfHeader format version (currently 1) |
| `0x06` | 1 | `program_type` | eBPF program type (`EbpfProgramType` enum) |
| `0x07` | 1 | `attach_type` | eBPF attach point (`EbpfAttachType` enum) |
| `0x08` | 4 | `program_flags` | Bitfield flags |
| `0x0C` | 2 | `insn_count` | Number of BPF instructions (max 65535) |
| `0x0E` | 2 | `max_dimension` | Maximum vector dimension this program handles |
| `0x10` | 8 | `program_size` | ELF object size (bytes) |
| `0x18` | 4 | `map_count` | Number of BPF maps defined |
| `0x1C` | 4 | `btf_size` | BTF (BPF Type Format) section size |
| `0x20` | 32 | `program_hash` | SHAKE-256-256 of the ELF object |
### Enums
#### `KernelArch` (`#[repr(u8)]`)
| Value | Name | Description |
|-------|------|-------------|
| `0x00` | `X86_64` | AMD64 / Intel 64 |
| `0x01` | `Aarch64` | ARM 64-bit (ARMv8-A and later) |
| `0x02` | `Riscv64` | RISC-V 64-bit (RV64GC) |
| `0xFE` | `Universal` | Architecture-independent (e.g., interpreted) |
| `0xFF` | `Unknown` | Reserved / unspecified |
#### `KernelType` (`#[repr(u8)]`)
| Value | Name | Description |
|-------|------|-------------|
| `0x00` | `Hermit` | Hermit OS unikernel (Rust-native) |
| `0x01` | `MicroLinux` | Minimal Linux kernel (bzImage compatible) |
| `0x02` | `Asterinas` | Asterinas framekernel (Linux ABI compatible) |
| `0x03` | `WasiPreview2` | WASI Preview 2 component |
| `0x04` | `Custom` | Custom kernel (requires external VMM knowledge) |
| `0xFE` | `TestStub` | Test stub for CI (boots, reports health, exits) |
| `0xFF` | `Reserved` | Reserved |
#### `ApiTransport` (`#[repr(u8)]`)
| Value | Name | Description |
|-------|------|-------------|
| `0x00` | `TcpHttp` | HTTP/1.1 over TCP (default) |
| `0x01` | `TcpGrpc` | gRPC over TCP (HTTP/2) |
| `0x02` | `Vsock` | VirtIO socket (Firecracker host-to-guest) |
| `0x03` | `SharedMem` | Shared memory region (same-host co-location) |
| `0xFF` | `None` | No network API (batch mode only) |
#### `EbpfProgramType` (`#[repr(u8)]`)
| Value | Name | Description |
|-------|------|-------------|
| `0x00` | `XdpDistance` | XDP program for distance computation on packets |
| `0x01` | `TcFilter` | TC classifier for query routing |
| `0x02` | `SocketFilter` | Socket filter for query preprocessing |
| `0x03` | `Tracepoint` | Tracepoint for performance monitoring |
| `0x04` | `Kprobe` | Kprobe for dynamic instrumentation |
| `0x05` | `CgroupSkb` | Cgroup socket buffer filter |
| `0xFF` | `Custom` | Custom program type |
#### `EbpfAttachType` (`#[repr(u8)]`)
| Value | Name | Description |
|-------|------|-------------|
| `0x00` | `XdpIngress` | XDP hook on NIC ingress |
| `0x01` | `TcIngress` | TC ingress qdisc |
| `0x02` | `TcEgress` | TC egress qdisc |
| `0x03` | `SocketFilter` | Socket filter attachment |
| `0x04` | `CgroupIngress` | Cgroup ingress |
| `0x05` | `CgroupEgress` | Cgroup egress |
| `0xFF` | `None` | No automatic attachment |
### `KernelFlags` Constants (u32 bitfield)
| Bit | Name | Description |
|-----|------|-------------|
| 0 | `REQUIRES_TEE` | Kernel must run inside a TEE enclave |
| 1 | `REQUIRES_KVM` | Kernel requires KVM (hardware virtualization) |
| 2 | `REQUIRES_UEFI` | Kernel requires UEFI boot |
| 3 | `HAS_NETWORKING` | Kernel includes network stack |
| 4 | `HAS_QUERY_API` | Kernel exposes RVF query API on `api_port` |
| 5 | `HAS_INGEST_API` | Kernel exposes RVF ingest API |
| 6 | `HAS_ADMIN_API` | Kernel exposes health/metrics API |
| 7 | `ATTESTATION_READY` | Kernel can generate TEE attestation quotes |
| 8 | `SIGNED` | Kernel image is signed (SignatureFooter follows) |
| 9 | `MEASURED` | Kernel measurement stored in WITNESS_SEG |
| 10 | `COMPRESSED` | Image is compressed (per compression field) |
| 11 | `RELOCATABLE` | Kernel is position-independent |
| 12 | `HAS_VIRTIO_NET` | Kernel includes VirtIO network driver |
| 13 | `HAS_VIRTIO_BLK` | Kernel includes VirtIO block driver |
| 14 | `HAS_VSOCK` | Kernel includes VSOCK for host communication |
| 15-31 | reserved | Reserved (must be zero) |
### Three-Tier Execution Model
RVF supports a three-tier execution model where a single `.rvf` file can carry compute at multiple levels:
| Tier | Segment | Typical Size | Target Environment | Boot Time |
|------|---------|-------------|--------------------|-----------|
| **1: WASM** | WASM_SEG (existing) | 5.5 KB | Browser, edge, IoT | <1 ms |
| **2: eBPF** | EBPF_SEG (`0x0F`) | 10-50 KB | Linux kernel fast path (XDP, TC) | <20 ms |
| **3: Unikernel** | KERNEL_SEG (`0x0E`) | 200 KB - 2 MB | TEE enclaves, Firecracker, bare metal | <125 ms |
Files without KERNEL_SEG or EBPF_SEG continue to work unchanged. Readers that do not recognize these segment types skip them per the RVF forward-compatibility rule. See [ADR-030](../../../docs/adr/ADR-030-rvf-computational-container.md) for the full specification.
## License
MIT OR Apache-2.0

View File

@@ -0,0 +1,962 @@
//! AGI Cognitive Container types (ADR-036).
//!
//! An AGI container is a single RVF file that packages the complete intelligence
//! runtime: micro Linux kernel, Claude Code orchestrator config, Claude Flow
//! swarm manager, RuVector world model, evaluation harness, witness chains,
//! and tool adapters.
//!
//! Wire format: 64-byte `AgiContainerHeader` + TLV manifest sections.
//! The header is stored as a META segment (SegmentType::Meta) in the RVF file,
//! alongside the KERNEL_SEG, WASM_SEG, VEC_SEG, INDEX_SEG, WITNESS_SEG, and
//! CRYPTO_SEG that hold the actual payload data.
/// Magic bytes for AGI container manifest: "RVAG" (RuVector AGI).
pub const AGI_MAGIC: u32 = 0x5256_4147;
/// Size of the AGI container header in bytes.
pub const AGI_HEADER_SIZE: usize = 64;
/// Maximum container size: 16 GiB. Prevents unbounded resource consumption.
pub const AGI_MAX_CONTAINER_SIZE: u64 = 16 * 1024 * 1024 * 1024;
// --- Flags ---
/// Container includes a KERNEL_SEG with micro Linux kernel.
pub const AGI_HAS_KERNEL: u16 = 1 << 0;
/// Container includes WASM_SEG modules.
pub const AGI_HAS_WASM: u16 = 1 << 1;
/// Container includes Claude Code + Claude Flow orchestrator config.
pub const AGI_HAS_ORCHESTRATOR: u16 = 1 << 2;
/// Container includes VEC_SEG + INDEX_SEG world model data.
pub const AGI_HAS_WORLD_MODEL: u16 = 1 << 3;
/// Container includes evaluation harness (task suite + graders).
pub const AGI_HAS_EVAL: u16 = 1 << 4;
/// Container includes promoted skill library.
pub const AGI_HAS_SKILLS: u16 = 1 << 5;
/// Container includes ADR-035 witness chain.
pub const AGI_HAS_WITNESS: u16 = 1 << 6;
/// Container is cryptographically signed (HMAC-SHA256 or Ed25519).
pub const AGI_SIGNED: u16 = 1 << 7;
/// All tool outputs stored — container supports replay mode.
pub const AGI_REPLAY_CAPABLE: u16 = 1 << 8;
/// Container can run without network (offline-first).
pub const AGI_OFFLINE_CAPABLE: u16 = 1 << 9;
/// Container includes MCP tool adapter registry.
pub const AGI_HAS_TOOLS: u16 = 1 << 10;
/// Container includes coherence gate configuration.
pub const AGI_HAS_COHERENCE_GATES: u16 = 1 << 11;
/// Container includes cross-domain transfer learning data.
pub const AGI_HAS_DOMAIN_EXPANSION: u16 = 1 << 12;
// --- TLV tags for the manifest payload ---
/// Container UUID.
pub const AGI_TAG_CONTAINER_ID: u16 = 0x0100;
/// Build UUID.
pub const AGI_TAG_BUILD_ID: u16 = 0x0101;
/// Pinned model identifier (UTF-8 string, e.g. "claude-opus-4-6").
pub const AGI_TAG_MODEL_ID: u16 = 0x0102;
/// Serialized governance policy (binary, per ADR-035).
pub const AGI_TAG_POLICY: u16 = 0x0103;
/// Claude Code + Claude Flow orchestrator config (JSON or TOML).
pub const AGI_TAG_ORCHESTRATOR: u16 = 0x0104;
/// MCP tool adapter registry (JSON array of tool schemas).
pub const AGI_TAG_TOOL_REGISTRY: u16 = 0x0105;
/// Agent role prompts (one per agent type).
pub const AGI_TAG_AGENT_PROMPTS: u16 = 0x0106;
/// Evaluation task suite (JSON array of task specs).
pub const AGI_TAG_EVAL_TASKS: u16 = 0x0107;
/// Grading rules (JSON or binary grader config).
pub const AGI_TAG_EVAL_GRADERS: u16 = 0x0108;
/// Promoted skill library (serialized skill nodes).
pub const AGI_TAG_SKILL_LIBRARY: u16 = 0x0109;
/// Replay automation script.
pub const AGI_TAG_REPLAY_SCRIPT: u16 = 0x010A;
/// Kernel boot parameters (command line, initrd config).
pub const AGI_TAG_KERNEL_CONFIG: u16 = 0x010B;
/// Network configuration (ports, endpoints, TLS).
pub const AGI_TAG_NETWORK_CONFIG: u16 = 0x010C;
/// Coherence gate thresholds and rules.
pub const AGI_TAG_COHERENCE_CONFIG: u16 = 0x010D;
/// Claude.md project instructions.
pub const AGI_TAG_PROJECT_INSTRUCTIONS: u16 = 0x010E;
/// Dependency snapshot hashes (pinned repos, packages).
pub const AGI_TAG_DEPENDENCY_SNAPSHOT: u16 = 0x010F;
/// Authority level and resource budget configuration.
pub const AGI_TAG_AUTHORITY_CONFIG: u16 = 0x0110;
/// Target domain profile identifier.
pub const AGI_TAG_DOMAIN_PROFILE: u16 = 0x0111;
/// Cross-domain transfer prior (posterior summaries).
pub const AGI_TAG_TRANSFER_PRIOR: u16 = 0x0112;
/// Policy kernel configuration and performance history.
pub const AGI_TAG_POLICY_KERNEL: u16 = 0x0113;
/// Cost curve convergence and acceleration data.
pub const AGI_TAG_COST_CURVE: u16 = 0x0114;
/// Counterexample archive (failed solutions for future decisions).
pub const AGI_TAG_COUNTEREXAMPLES: u16 = 0x0115;
// --- Execution mode ---
/// Container execution mode.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum ExecutionMode {
/// Replay: no external tool calls, use stored receipts.
/// All graders must match exactly. Witness chain must match.
Replay = 0,
/// Verify: live tool calls, outputs stored and hashed.
/// Outputs must pass same tests. Costs within expected bounds.
Verify = 1,
/// Live: full autonomous operation with governance controls.
Live = 2,
}
impl TryFrom<u8> for ExecutionMode {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Replay),
1 => Ok(Self::Verify),
2 => Ok(Self::Live),
other => Err(other),
}
}
}
// --- Authority level ---
/// Authority level controlling what actions a container execution can perform.
///
/// Each action in the world model must reference a policy decision node
/// that grants at least the required authority level.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum AuthorityLevel {
/// Read-only: query vectors, graphs, memories. No mutations.
ReadOnly = 0,
/// Write to internal memory: commit world model deltas behind
/// coherence gates. No external tool calls.
WriteMemory = 1,
/// Execute tools: run sandboxed tools (file read/write, tests,
/// code generation). External side effects gated by policy.
ExecuteTools = 2,
/// Write external: push code, create PRs, send messages, modify
/// infrastructure. Requires explicit policy grant per action class.
WriteExternal = 3,
}
impl TryFrom<u8> for AuthorityLevel {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::ReadOnly),
1 => Ok(Self::WriteMemory),
2 => Ok(Self::ExecuteTools),
3 => Ok(Self::WriteExternal),
other => Err(other),
}
}
}
impl AuthorityLevel {
/// Default authority for the given execution mode.
pub const fn default_for_mode(mode: ExecutionMode) -> Self {
match mode {
ExecutionMode::Replay => Self::ReadOnly,
ExecutionMode::Verify => Self::ExecuteTools,
ExecutionMode::Live => Self::WriteMemory,
}
}
/// Check if this authority level permits a given required level.
pub const fn permits(&self, required: AuthorityLevel) -> bool {
(*self as u8) >= (required as u8)
}
}
// --- Resource budgets ---
/// Per-task resource budget with hard caps.
///
/// Budget exhaustion triggers graceful degradation: the task enters `Skipped`
/// outcome with a `BudgetExhausted` postmortem in the witness bundle.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ResourceBudget {
/// Maximum wall-clock time per task in seconds. Default: 300.
pub max_time_secs: u32,
/// Maximum total model tokens per task. Default: 200,000.
pub max_tokens: u32,
/// Maximum cost per task in microdollars. Default: 1,000,000 ($1.00).
pub max_cost_microdollars: u32,
/// Maximum tool calls per task. Default: 50.
pub max_tool_calls: u16,
/// Maximum external write actions per task. Default: 0.
pub max_external_writes: u16,
}
impl Default for ResourceBudget {
fn default() -> Self {
Self::DEFAULT
}
}
impl ResourceBudget {
/// Default resource budget for a single task.
pub const DEFAULT: Self = Self {
max_time_secs: 300,
max_tokens: 200_000,
max_cost_microdollars: 1_000_000,
max_tool_calls: 50,
max_external_writes: 0,
};
/// Extended budget (4x default) for high-value tasks.
pub const EXTENDED: Self = Self {
max_time_secs: 1200,
max_tokens: 800_000,
max_cost_microdollars: 4_000_000,
max_tool_calls: 200,
max_external_writes: 10,
};
/// Maximum configurable budget (hard ceiling, not overridable).
pub const MAX: Self = Self {
max_time_secs: 3600,
max_tokens: 1_000_000,
max_cost_microdollars: 10_000_000,
max_tool_calls: 500,
max_external_writes: 50,
};
/// Clamp this budget to not exceed the MAX limits.
pub const fn clamped(self) -> Self {
Self {
max_time_secs: if self.max_time_secs > Self::MAX.max_time_secs {
Self::MAX.max_time_secs
} else {
self.max_time_secs
},
max_tokens: if self.max_tokens > Self::MAX.max_tokens {
Self::MAX.max_tokens
} else {
self.max_tokens
},
max_cost_microdollars: if self.max_cost_microdollars > Self::MAX.max_cost_microdollars {
Self::MAX.max_cost_microdollars
} else {
self.max_cost_microdollars
},
max_tool_calls: if self.max_tool_calls > Self::MAX.max_tool_calls {
Self::MAX.max_tool_calls
} else {
self.max_tool_calls
},
max_external_writes: if self.max_external_writes > Self::MAX.max_external_writes {
Self::MAX.max_external_writes
} else {
self.max_external_writes
},
}
}
}
// --- Coherence thresholds ---
/// Configurable coherence thresholds for structural health gating.
///
/// These map to ADR-033's quality framework: the coherence score is analogous
/// to `ResponseQuality` -- it signals whether the system's internal state is
/// trustworthy enough to act on.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CoherenceThresholds {
/// Minimum coherence score (0.0 to 1.0). Below this, block all commits
/// and enter repair mode. Default: 0.70.
pub min_coherence_score: f32,
/// Maximum contradiction rate (contradictions per 100 events).
/// Above this, freeze skill promotion. Default: 5.0.
pub max_contradiction_rate: f32,
/// Maximum rollback ratio (fraction of tasks that required rollback).
/// Above this, halt Live execution; require human review. Default: 0.20.
pub max_rollback_ratio: f32,
}
impl Default for CoherenceThresholds {
fn default() -> Self {
Self::DEFAULT
}
}
impl CoherenceThresholds {
/// Default coherence thresholds.
pub const DEFAULT: Self = Self {
min_coherence_score: 0.70,
max_contradiction_rate: 5.0,
max_rollback_ratio: 0.20,
};
/// Strict thresholds for production.
pub const STRICT: Self = Self {
min_coherence_score: 0.85,
max_contradiction_rate: 2.0,
max_rollback_ratio: 0.10,
};
/// Validate that threshold values are within valid ranges.
pub fn validate(&self) -> Result<(), ContainerError> {
if self.min_coherence_score < 0.0 || self.min_coherence_score > 1.0 {
return Err(ContainerError::InvalidConfig(
"min_coherence_score must be in [0.0, 1.0]",
));
}
if self.max_contradiction_rate < 0.0 {
return Err(ContainerError::InvalidConfig(
"max_contradiction_rate must be >= 0.0",
));
}
if self.max_rollback_ratio < 0.0 || self.max_rollback_ratio > 1.0 {
return Err(ContainerError::InvalidConfig(
"max_rollback_ratio must be in [0.0, 1.0]",
));
}
Ok(())
}
}
/// Wire-format AGI container header (exactly 64 bytes, `repr(C)`).
///
/// ```text
/// Offset Type Field
/// 0x00 u32 magic (0x52564147 "RVAG")
/// 0x04 u16 version
/// 0x06 u16 flags
/// 0x08 [u8; 16] container_id (UUID)
/// 0x18 [u8; 16] build_id (UUID)
/// 0x28 u64 created_ns (UNIX epoch nanoseconds)
/// 0x30 [u8; 8] model_id_hash (SHA-256 truncated)
/// 0x38 [u8; 8] policy_hash (SHA-256 truncated)
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct AgiContainerHeader {
/// Magic bytes: AGI_MAGIC.
pub magic: u32,
/// Format version (currently 1).
pub version: u16,
/// Bitfield flags indicating which segments are present.
pub flags: u16,
/// Unique container identifier (UUID).
pub container_id: [u8; 16],
/// Build identifier (UUID, changes on each repackaging).
pub build_id: [u8; 16],
/// Creation timestamp (nanoseconds since UNIX epoch).
pub created_ns: u64,
/// SHA-256 of the pinned model identifier, truncated to 8 bytes.
pub model_id_hash: [u8; 8],
/// SHA-256 of the governance policy, truncated to 8 bytes.
pub policy_hash: [u8; 8],
}
// Compile-time size assertion.
const _: () = assert!(core::mem::size_of::<AgiContainerHeader>() == 64);
impl AgiContainerHeader {
/// Check magic bytes.
pub const fn is_valid_magic(&self) -> bool {
self.magic == AGI_MAGIC
}
/// Check if the container is signed.
pub const fn is_signed(&self) -> bool {
self.flags & AGI_SIGNED != 0
}
/// Check if the container has a micro Linux kernel.
pub const fn has_kernel(&self) -> bool {
self.flags & AGI_HAS_KERNEL != 0
}
/// Check if the container has an orchestrator config.
pub const fn has_orchestrator(&self) -> bool {
self.flags & AGI_HAS_ORCHESTRATOR != 0
}
/// Check if the container supports replay mode.
pub const fn is_replay_capable(&self) -> bool {
self.flags & AGI_REPLAY_CAPABLE != 0
}
/// Check if the container can run offline.
pub const fn is_offline_capable(&self) -> bool {
self.flags & AGI_OFFLINE_CAPABLE != 0
}
/// Check if the container has a world model (VEC + INDEX segments).
pub const fn has_world_model(&self) -> bool {
self.flags & AGI_HAS_WORLD_MODEL != 0
}
/// Check if the container has coherence gate configuration.
pub const fn has_coherence_gates(&self) -> bool {
self.flags & AGI_HAS_COHERENCE_GATES != 0
}
/// Check if the container has domain expansion data.
pub const fn has_domain_expansion(&self) -> bool {
self.flags & AGI_HAS_DOMAIN_EXPANSION != 0
}
/// Serialize header to a 64-byte array.
pub fn to_bytes(&self) -> [u8; AGI_HEADER_SIZE] {
let mut buf = [0u8; AGI_HEADER_SIZE];
buf[0..4].copy_from_slice(&self.magic.to_le_bytes());
buf[4..6].copy_from_slice(&self.version.to_le_bytes());
buf[6..8].copy_from_slice(&self.flags.to_le_bytes());
buf[8..24].copy_from_slice(&self.container_id);
buf[24..40].copy_from_slice(&self.build_id);
buf[40..48].copy_from_slice(&self.created_ns.to_le_bytes());
buf[48..56].copy_from_slice(&self.model_id_hash);
buf[56..64].copy_from_slice(&self.policy_hash);
buf
}
/// Deserialize header from a byte slice (>= 64 bytes).
pub fn from_bytes(data: &[u8]) -> Result<Self, crate::RvfError> {
if data.len() < AGI_HEADER_SIZE {
return Err(crate::RvfError::SizeMismatch {
expected: AGI_HEADER_SIZE,
got: data.len(),
});
}
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != AGI_MAGIC {
return Err(crate::RvfError::BadMagic {
expected: AGI_MAGIC,
got: magic,
});
}
let mut container_id = [0u8; 16];
container_id.copy_from_slice(&data[8..24]);
let mut build_id = [0u8; 16];
build_id.copy_from_slice(&data[24..40]);
let mut model_id_hash = [0u8; 8];
model_id_hash.copy_from_slice(&data[48..56]);
let mut policy_hash = [0u8; 8];
policy_hash.copy_from_slice(&data[56..64]);
Ok(Self {
magic,
version: u16::from_le_bytes([data[4], data[5]]),
flags: u16::from_le_bytes([data[6], data[7]]),
container_id,
build_id,
created_ns: u64::from_le_bytes([
data[40], data[41], data[42], data[43], data[44], data[45], data[46], data[47],
]),
model_id_hash,
policy_hash,
})
}
}
/// Required segments for a valid AGI container.
///
/// Used by the container builder/validator to ensure completeness.
#[derive(Clone, Debug, Default)]
pub struct ContainerSegments {
/// KERNEL_SEG: micro Linux kernel (e.g. Firecracker-compatible vmlinux).
pub kernel_present: bool,
/// KERNEL_SEG size in bytes.
pub kernel_size: u64,
/// WASM_SEG: interpreter + microkernel modules.
pub wasm_count: u16,
/// Total WASM_SEG size in bytes.
pub wasm_total_size: u64,
/// VEC_SEG: world model vector count.
pub vec_segment_count: u16,
/// INDEX_SEG: HNSW index count.
pub index_segment_count: u16,
/// WITNESS_SEG: witness bundle count.
pub witness_count: u32,
/// CRYPTO_SEG: present.
pub crypto_present: bool,
/// META segment with AGI manifest: present.
pub manifest_present: bool,
/// Orchestrator configuration present.
pub orchestrator_present: bool,
/// World model data present (VEC + INDEX segments).
pub world_model_present: bool,
/// Domain expansion (transfer priors, policy kernels, cost curves) present.
pub domain_expansion_present: bool,
/// Total container size in bytes.
pub total_size: u64,
}
impl ContainerSegments {
/// Validate that the container has all required segments for a given
/// execution mode.
pub fn validate(&self, mode: ExecutionMode) -> Result<(), ContainerError> {
// All modes require the manifest.
if !self.manifest_present {
return Err(ContainerError::MissingSegment("AGI manifest"));
}
// Size check.
if self.total_size > AGI_MAX_CONTAINER_SIZE {
return Err(ContainerError::TooLarge {
size: self.total_size,
});
}
match mode {
ExecutionMode::Replay => {
// Replay needs witness chains.
if self.witness_count == 0 {
return Err(ContainerError::MissingSegment("witness chain"));
}
}
ExecutionMode::Verify | ExecutionMode::Live => {
// Verify/Live need at least kernel or WASM.
if !self.kernel_present && self.wasm_count == 0 {
return Err(ContainerError::MissingSegment("kernel or WASM runtime"));
}
// Verify/Live need world model data for meaningful operation.
if !self.world_model_present
&& self.vec_segment_count == 0
&& self.index_segment_count == 0
{
return Err(ContainerError::MissingSegment(
"world model (VEC or INDEX segments)",
));
}
}
}
Ok(())
}
/// Compute the flags bitfield from present segments.
pub fn to_flags(&self) -> u16 {
let mut flags: u16 = 0;
if self.kernel_present {
flags |= AGI_HAS_KERNEL;
}
if self.wasm_count > 0 {
flags |= AGI_HAS_WASM;
}
if self.witness_count > 0 {
flags |= AGI_HAS_WITNESS;
}
if self.crypto_present {
flags |= AGI_SIGNED;
}
if self.orchestrator_present {
flags |= AGI_HAS_ORCHESTRATOR;
}
if self.world_model_present || self.vec_segment_count > 0 || self.index_segment_count > 0 {
flags |= AGI_HAS_WORLD_MODEL;
}
if self.domain_expansion_present {
flags |= AGI_HAS_DOMAIN_EXPANSION;
}
flags
}
}
/// Error type for AGI container operations.
#[derive(Debug, PartialEq, Eq)]
pub enum ContainerError {
/// A required segment is missing.
MissingSegment(&'static str),
/// Container exceeds size limit.
TooLarge { size: u64 },
/// Invalid segment configuration.
InvalidConfig(&'static str),
/// Signature verification failed.
SignatureInvalid,
/// Authority level insufficient for the requested action.
InsufficientAuthority { required: u8, granted: u8 },
/// Resource budget exceeded.
BudgetExhausted(&'static str),
}
impl core::fmt::Display for ContainerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ContainerError::MissingSegment(s) => write!(f, "missing segment: {s}"),
ContainerError::TooLarge { size } => {
write!(f, "container too large: {size} bytes")
}
ContainerError::InvalidConfig(s) => write!(f, "invalid config: {s}"),
ContainerError::SignatureInvalid => {
write!(f, "signature verification failed")
}
ContainerError::InsufficientAuthority { required, granted } => {
write!(
f,
"insufficient authority: required level {required}, granted {granted}"
)
}
ContainerError::BudgetExhausted(resource) => {
write!(f, "resource budget exhausted: {resource}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn agi_header_size() {
assert_eq!(core::mem::size_of::<AgiContainerHeader>(), 64);
}
#[test]
fn agi_header_round_trip() {
let hdr = AgiContainerHeader {
magic: AGI_MAGIC,
version: 1,
flags: AGI_HAS_KERNEL
| AGI_HAS_ORCHESTRATOR
| AGI_HAS_WORLD_MODEL
| AGI_HAS_EVAL
| AGI_SIGNED
| AGI_REPLAY_CAPABLE,
container_id: [0x42; 16],
build_id: [0x43; 16],
created_ns: 1_700_000_000_000_000_000,
model_id_hash: [0xAA; 8],
policy_hash: [0xBB; 8],
};
let bytes = hdr.to_bytes();
assert_eq!(bytes.len(), AGI_HEADER_SIZE);
let decoded = AgiContainerHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded, hdr);
}
#[test]
fn agi_header_bad_magic() {
let mut bytes = [0u8; 64];
bytes[0..4].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
assert!(AgiContainerHeader::from_bytes(&bytes).is_err());
}
#[test]
fn agi_header_too_short() {
assert!(AgiContainerHeader::from_bytes(&[0u8; 32]).is_err());
}
#[test]
fn agi_flags() {
let hdr = AgiContainerHeader {
magic: AGI_MAGIC,
version: 1,
flags: AGI_HAS_KERNEL | AGI_HAS_ORCHESTRATOR | AGI_SIGNED,
container_id: [0; 16],
build_id: [0; 16],
created_ns: 0,
model_id_hash: [0; 8],
policy_hash: [0; 8],
};
assert!(hdr.has_kernel());
assert!(hdr.has_orchestrator());
assert!(hdr.is_signed());
assert!(!hdr.is_replay_capable());
assert!(!hdr.is_offline_capable());
assert!(!hdr.has_world_model());
assert!(!hdr.has_coherence_gates());
}
#[test]
fn execution_mode_round_trip() {
for raw in 0..=2u8 {
let m = ExecutionMode::try_from(raw).unwrap();
assert_eq!(m as u8, raw);
}
assert!(ExecutionMode::try_from(3).is_err());
}
#[test]
fn segments_validate_replay_needs_witness() {
let segs = ContainerSegments {
manifest_present: true,
witness_count: 0,
..Default::default()
};
assert_eq!(
segs.validate(ExecutionMode::Replay),
Err(ContainerError::MissingSegment("witness chain"))
);
}
#[test]
fn segments_validate_live_needs_runtime() {
let segs = ContainerSegments {
manifest_present: true,
kernel_present: false,
wasm_count: 0,
..Default::default()
};
assert_eq!(
segs.validate(ExecutionMode::Live),
Err(ContainerError::MissingSegment("kernel or WASM runtime"))
);
}
#[test]
fn segments_validate_live_needs_world_model() {
let segs = ContainerSegments {
manifest_present: true,
kernel_present: true,
vec_segment_count: 0,
index_segment_count: 0,
world_model_present: false,
..Default::default()
};
assert_eq!(
segs.validate(ExecutionMode::Live),
Err(ContainerError::MissingSegment(
"world model (VEC or INDEX segments)"
))
);
}
#[test]
fn segments_validate_live_with_kernel_and_world_model() {
let segs = ContainerSegments {
manifest_present: true,
kernel_present: true,
world_model_present: true,
..Default::default()
};
assert!(segs.validate(ExecutionMode::Live).is_ok());
}
#[test]
fn segments_validate_live_with_wasm_and_vec() {
let segs = ContainerSegments {
manifest_present: true,
wasm_count: 2,
vec_segment_count: 1,
..Default::default()
};
assert!(segs.validate(ExecutionMode::Live).is_ok());
}
#[test]
fn segments_validate_replay_with_witness() {
let segs = ContainerSegments {
manifest_present: true,
witness_count: 10,
..Default::default()
};
assert!(segs.validate(ExecutionMode::Replay).is_ok());
}
#[test]
fn segments_validate_too_large() {
let segs = ContainerSegments {
manifest_present: true,
total_size: AGI_MAX_CONTAINER_SIZE + 1,
..Default::default()
};
assert_eq!(
segs.validate(ExecutionMode::Replay),
Err(ContainerError::TooLarge {
size: AGI_MAX_CONTAINER_SIZE + 1
})
);
}
#[test]
fn segments_to_flags() {
let segs = ContainerSegments {
kernel_present: true,
wasm_count: 1,
witness_count: 5,
crypto_present: true,
orchestrator_present: true,
vec_segment_count: 3,
..Default::default()
};
let flags = segs.to_flags();
assert_ne!(flags & AGI_HAS_KERNEL, 0);
assert_ne!(flags & AGI_HAS_WASM, 0);
assert_ne!(flags & AGI_HAS_WITNESS, 0);
assert_ne!(flags & AGI_SIGNED, 0);
assert_ne!(flags & AGI_HAS_ORCHESTRATOR, 0);
assert_ne!(flags & AGI_HAS_WORLD_MODEL, 0);
}
#[test]
fn container_error_display() {
let e = ContainerError::MissingSegment("kernel");
assert!(format!("{e}").contains("kernel"));
let e2 = ContainerError::TooLarge { size: 999 };
assert!(format!("{e2}").contains("999"));
let e3 = ContainerError::InsufficientAuthority {
required: 3,
granted: 1,
};
assert!(format!("{e3}").contains("required level 3"));
let e4 = ContainerError::BudgetExhausted("tokens");
assert!(format!("{e4}").contains("tokens"));
}
// --- Authority level tests ---
#[test]
fn authority_level_round_trip() {
for raw in 0..=3u8 {
let a = AuthorityLevel::try_from(raw).unwrap();
assert_eq!(a as u8, raw);
}
assert!(AuthorityLevel::try_from(4).is_err());
}
#[test]
fn authority_level_ordering() {
assert!(AuthorityLevel::ReadOnly < AuthorityLevel::WriteMemory);
assert!(AuthorityLevel::WriteMemory < AuthorityLevel::ExecuteTools);
assert!(AuthorityLevel::ExecuteTools < AuthorityLevel::WriteExternal);
}
#[test]
fn authority_permits() {
assert!(AuthorityLevel::WriteExternal.permits(AuthorityLevel::ReadOnly));
assert!(AuthorityLevel::WriteExternal.permits(AuthorityLevel::WriteExternal));
assert!(AuthorityLevel::ExecuteTools.permits(AuthorityLevel::WriteMemory));
assert!(!AuthorityLevel::ReadOnly.permits(AuthorityLevel::WriteMemory));
assert!(!AuthorityLevel::WriteMemory.permits(AuthorityLevel::ExecuteTools));
}
#[test]
fn authority_default_for_mode() {
assert_eq!(
AuthorityLevel::default_for_mode(ExecutionMode::Replay),
AuthorityLevel::ReadOnly
);
assert_eq!(
AuthorityLevel::default_for_mode(ExecutionMode::Verify),
AuthorityLevel::ExecuteTools
);
assert_eq!(
AuthorityLevel::default_for_mode(ExecutionMode::Live),
AuthorityLevel::WriteMemory
);
}
// --- Resource budget tests ---
#[test]
fn resource_budget_default() {
let b = ResourceBudget::default();
assert_eq!(b.max_time_secs, 300);
assert_eq!(b.max_tokens, 200_000);
assert_eq!(b.max_cost_microdollars, 1_000_000);
assert_eq!(b.max_tool_calls, 50);
assert_eq!(b.max_external_writes, 0);
}
#[test]
fn resource_budget_clamped() {
let over = ResourceBudget {
max_time_secs: 999_999,
max_tokens: 999_999_999,
max_cost_microdollars: 999_999_999,
max_tool_calls: 60_000,
max_external_writes: 60_000,
};
let clamped = over.clamped();
assert_eq!(clamped.max_time_secs, ResourceBudget::MAX.max_time_secs);
assert_eq!(clamped.max_tokens, ResourceBudget::MAX.max_tokens);
assert_eq!(
clamped.max_cost_microdollars,
ResourceBudget::MAX.max_cost_microdollars
);
assert_eq!(clamped.max_tool_calls, ResourceBudget::MAX.max_tool_calls);
assert_eq!(
clamped.max_external_writes,
ResourceBudget::MAX.max_external_writes
);
}
#[test]
fn resource_budget_within_max_unchanged() {
let within = ResourceBudget::DEFAULT;
let clamped = within.clamped();
assert_eq!(clamped, within);
}
// --- Coherence threshold tests ---
#[test]
fn coherence_thresholds_default() {
let ct = CoherenceThresholds::default();
assert!((ct.min_coherence_score - 0.70).abs() < f32::EPSILON);
assert!((ct.max_contradiction_rate - 5.0).abs() < f32::EPSILON);
assert!((ct.max_rollback_ratio - 0.20).abs() < f32::EPSILON);
}
#[test]
fn coherence_thresholds_strict() {
let ct = CoherenceThresholds::STRICT;
assert!((ct.min_coherence_score - 0.85).abs() < f32::EPSILON);
assert!((ct.max_contradiction_rate - 2.0).abs() < f32::EPSILON);
assert!((ct.max_rollback_ratio - 0.10).abs() < f32::EPSILON);
}
#[test]
fn coherence_thresholds_validate_valid() {
assert!(CoherenceThresholds::DEFAULT.validate().is_ok());
assert!(CoherenceThresholds::STRICT.validate().is_ok());
}
#[test]
fn coherence_thresholds_validate_bad_score() {
let ct = CoherenceThresholds {
min_coherence_score: 1.5,
..CoherenceThresholds::DEFAULT
};
assert_eq!(
ct.validate(),
Err(ContainerError::InvalidConfig(
"min_coherence_score must be in [0.0, 1.0]"
))
);
}
#[test]
fn coherence_thresholds_validate_negative_rate() {
let ct = CoherenceThresholds {
max_contradiction_rate: -1.0,
..CoherenceThresholds::DEFAULT
};
assert_eq!(
ct.validate(),
Err(ContainerError::InvalidConfig(
"max_contradiction_rate must be >= 0.0"
))
);
}
#[test]
fn coherence_thresholds_validate_bad_ratio() {
let ct = CoherenceThresholds {
max_rollback_ratio: 2.0,
..CoherenceThresholds::DEFAULT
};
assert_eq!(
ct.validate(),
Err(ContainerError::InvalidConfig(
"max_rollback_ratio must be in [0.0, 1.0]"
))
);
}
}

View File

@@ -0,0 +1,284 @@
//! Attestation types for Confidential Computing integration.
//!
//! These types describe hardware TEE platforms, attestation metadata,
//! and the wire format for attestation records stored in WITNESS_SEG
//! and CRYPTO_SEG payloads.
/// Hardware TEE platform identifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum TeePlatform {
/// Intel SGX.
Sgx = 0,
/// AMD SEV-SNP.
SevSnp = 1,
/// Intel TDX.
Tdx = 2,
/// ARM CCA.
ArmCca = 3,
/// Software-emulated (testing only).
SoftwareTee = 0xFE,
}
impl TryFrom<u8> for TeePlatform {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Sgx),
1 => Ok(Self::SevSnp),
2 => Ok(Self::Tdx),
3 => Ok(Self::ArmCca),
0xFE => Ok(Self::SoftwareTee),
other => Err(other),
}
}
}
/// Attestation witness type discriminant.
///
/// These extend the existing witness_type values (0x01=PROVENANCE,
/// 0x02=COMPUTATION used by claude-flow adapter).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum AttestationWitnessType {
/// TEE identity and measurement.
PlatformAttestation = 0x05,
/// Encryption key bound to TEE measurement.
KeyBinding = 0x06,
/// Operations performed inside the TEE.
ComputationProof = 0x07,
/// Chain of custody from model to TEE to RVF.
DataProvenance = 0x08,
}
impl TryFrom<u8> for AttestationWitnessType {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x05 => Ok(Self::PlatformAttestation),
0x06 => Ok(Self::KeyBinding),
0x07 => Ok(Self::ComputationProof),
0x08 => Ok(Self::DataProvenance),
other => Err(other),
}
}
}
/// Key type for keys bound to a TEE measurement.
pub const KEY_TYPE_TEE_BOUND: u8 = 4;
/// Wire-format attestation header (exactly 112 bytes, `repr(C)`).
///
/// ```text
/// Offset Type Field
/// 0x00 u8 platform (TeePlatform discriminant)
/// 0x01 u8 attestation_type (AttestationWitnessType discriminant)
/// 0x02 u16 quote_length (LE, length of opaque quote blob)
/// 0x04 u32 reserved_0 (must be zero)
/// 0x08 [u8; 32] measurement (MRENCLAVE / launch digest)
/// 0x28 [u8; 32] signer_id (MRSIGNER / author key hash)
/// 0x48 u64 timestamp_ns (LE, when attestation was captured)
/// 0x50 [u8; 16] nonce (anti-replay nonce)
/// 0x60 u16 svn (LE, security version number)
/// 0x62 u16 sig_algo (LE, SignatureAlgo of the quote)
/// 0x64 u8 flags (attestation flags)
/// 0x65 [u8; 3] reserved_1 (must be zero)
/// 0x68 u64 report_data_len (LE, length of custom report data)
/// ```
///
/// Total: 112 bytes (0x70).
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct AttestationHeader {
/// TEE platform discriminant (see [`TeePlatform`]).
pub platform: u8,
/// Attestation witness type discriminant (see [`AttestationWitnessType`]).
pub attestation_type: u8,
/// Length of the opaque quote blob (little-endian).
pub quote_length: u16,
/// Reserved, must be zero.
pub reserved_0: u32,
/// MRENCLAVE / launch digest.
pub measurement: [u8; 32],
/// MRSIGNER / author key hash.
pub signer_id: [u8; 32],
/// Timestamp in nanoseconds when attestation was captured (little-endian).
pub timestamp_ns: u64,
/// Anti-replay nonce.
pub nonce: [u8; 16],
/// Security version number (little-endian).
pub svn: u16,
/// Signature algorithm of the quote (little-endian).
pub sig_algo: u16,
/// Attestation flags.
pub flags: u8,
/// Reserved, must be zero.
pub reserved_1: [u8; 3],
/// Length of custom report data (little-endian).
pub report_data_len: u64,
}
// Compile-time size assertion.
const _: () = assert!(core::mem::size_of::<AttestationHeader>() == 112);
impl AttestationHeader {
/// TEE is in debug mode.
pub const FLAG_DEBUGGABLE: u8 = 0x01;
/// Custom report data present.
pub const FLAG_HAS_REPORT_DATA: u8 = 0x02;
/// Combined from multiple TEEs.
pub const FLAG_MULTI_PLATFORM: u8 = 0x04;
/// Create a new attestation header with all fields zeroed except
/// `platform` and `attestation_type`.
pub const fn new(platform: u8, attestation_type: u8) -> Self {
Self {
platform,
attestation_type,
quote_length: 0,
reserved_0: 0,
measurement: [0u8; 32],
signer_id: [0u8; 32],
timestamp_ns: 0,
nonce: [0u8; 16],
svn: 0,
sig_algo: 0,
flags: 0,
reserved_1: [0u8; 3],
report_data_len: 0,
}
}
/// Returns `true` if the TEE is in debug mode.
pub const fn is_debuggable(&self) -> bool {
self.flags & Self::FLAG_DEBUGGABLE != 0
}
/// Returns `true` if custom report data is present.
pub const fn has_report_data(&self) -> bool {
self.flags & Self::FLAG_HAS_REPORT_DATA != 0
}
/// Returns the total record length:
/// 112 (header) + report_data_len + quote_length.
pub const fn total_record_length(&self) -> u64 {
112 + self.report_data_len + self.quote_length as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tee_platform_round_trip() {
let variants: &[(u8, TeePlatform)] = &[
(0, TeePlatform::Sgx),
(1, TeePlatform::SevSnp),
(2, TeePlatform::Tdx),
(3, TeePlatform::ArmCca),
(0xFE, TeePlatform::SoftwareTee),
];
for &(raw, expected) in variants {
let parsed = TeePlatform::try_from(raw).unwrap();
assert_eq!(parsed, expected);
assert_eq!(parsed as u8, raw);
}
}
#[test]
fn tee_platform_invalid() {
assert_eq!(TeePlatform::try_from(4), Err(4));
assert_eq!(TeePlatform::try_from(0xFF), Err(0xFF));
assert_eq!(TeePlatform::try_from(0x80), Err(0x80));
}
#[test]
fn attestation_witness_type_round_trip() {
let variants: &[(u8, AttestationWitnessType)] = &[
(0x05, AttestationWitnessType::PlatformAttestation),
(0x06, AttestationWitnessType::KeyBinding),
(0x07, AttestationWitnessType::ComputationProof),
(0x08, AttestationWitnessType::DataProvenance),
];
for &(raw, expected) in variants {
let parsed = AttestationWitnessType::try_from(raw).unwrap();
assert_eq!(parsed, expected);
assert_eq!(parsed as u8, raw);
}
}
#[test]
fn attestation_witness_type_invalid() {
assert_eq!(AttestationWitnessType::try_from(0x00), Err(0x00));
assert_eq!(AttestationWitnessType::try_from(0x04), Err(0x04));
assert_eq!(AttestationWitnessType::try_from(0x09), Err(0x09));
assert_eq!(AttestationWitnessType::try_from(0xFF), Err(0xFF));
}
#[test]
fn attestation_header_size() {
assert_eq!(core::mem::size_of::<AttestationHeader>(), 112);
}
#[test]
fn attestation_header_new() {
let hdr = AttestationHeader::new(
TeePlatform::SevSnp as u8,
AttestationWitnessType::PlatformAttestation as u8,
);
assert_eq!(hdr.platform, 1);
assert_eq!(hdr.attestation_type, 0x05);
assert_eq!(hdr.quote_length, 0);
assert_eq!(hdr.reserved_0, 0);
assert_eq!(hdr.measurement, [0u8; 32]);
assert_eq!(hdr.signer_id, [0u8; 32]);
assert_eq!(hdr.timestamp_ns, 0);
assert_eq!(hdr.nonce, [0u8; 16]);
assert_eq!(hdr.svn, 0);
assert_eq!(hdr.sig_algo, 0);
assert_eq!(hdr.flags, 0);
assert_eq!(hdr.reserved_1, [0u8; 3]);
assert_eq!(hdr.report_data_len, 0);
}
#[test]
fn flag_is_debuggable() {
let mut hdr = AttestationHeader::new(0, 0);
assert!(!hdr.is_debuggable());
hdr.flags = AttestationHeader::FLAG_DEBUGGABLE;
assert!(hdr.is_debuggable());
// combined flags
hdr.flags = AttestationHeader::FLAG_DEBUGGABLE | AttestationHeader::FLAG_HAS_REPORT_DATA;
assert!(hdr.is_debuggable());
}
#[test]
fn flag_has_report_data() {
let mut hdr = AttestationHeader::new(0, 0);
assert!(!hdr.has_report_data());
hdr.flags = AttestationHeader::FLAG_HAS_REPORT_DATA;
assert!(hdr.has_report_data());
}
#[test]
fn total_record_length() {
let mut hdr = AttestationHeader::new(0, 0);
assert_eq!(hdr.total_record_length(), 112);
hdr.quote_length = 256;
assert_eq!(hdr.total_record_length(), 112 + 256);
hdr.report_data_len = 64;
assert_eq!(hdr.total_record_length(), 112 + 64 + 256);
}
#[test]
fn key_type_tee_bound_value() {
assert_eq!(KEY_TYPE_TEE_BOUND, 4);
}
}

View File

@@ -0,0 +1,44 @@
//! Checksum / hash algorithm identifiers.
/// Identifies the hash algorithm used for segment content verification.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum ChecksumAlgo {
/// CRC32C (SSE4.2 hardware-accelerated). Output: 4 bytes, zero-padded to 16.
Crc32c = 0,
/// XXH3-128. Output: 16 bytes. Fast, good distribution.
Xxh3_128 = 1,
/// SHAKE-256 (first 128 bits). Post-quantum safe, cryptographic.
Shake256 = 2,
}
impl TryFrom<u8> for ChecksumAlgo {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Crc32c),
1 => Ok(Self::Xxh3_128),
2 => Ok(Self::Shake256),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
assert_eq!(ChecksumAlgo::try_from(0), Ok(ChecksumAlgo::Crc32c));
assert_eq!(ChecksumAlgo::try_from(1), Ok(ChecksumAlgo::Xxh3_128));
assert_eq!(ChecksumAlgo::try_from(2), Ok(ChecksumAlgo::Shake256));
}
#[test]
fn invalid_value() {
assert_eq!(ChecksumAlgo::try_from(3), Err(3));
}
}

View File

@@ -0,0 +1,48 @@
//! Compression algorithm identifiers.
/// Identifies the compression algorithm applied to a segment payload.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum CompressionAlgo {
/// No compression.
None = 0,
/// LZ4 block compression (~4 GB/s decompress).
Lz4 = 1,
/// Zstandard compression (~1.5 GB/s decompress, higher ratio).
Zstd = 2,
/// Domain-specific custom compression.
Custom = 3,
}
impl TryFrom<u8> for CompressionAlgo {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::None),
1 => Ok(Self::Lz4),
2 => Ok(Self::Zstd),
3 => Ok(Self::Custom),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
for raw in 0..=3u8 {
let algo = CompressionAlgo::try_from(raw).unwrap();
assert_eq!(algo as u8, raw);
}
}
#[test]
fn invalid_value() {
assert_eq!(CompressionAlgo::try_from(4), Err(4));
}
}

View File

@@ -0,0 +1,52 @@
//! Magic numbers, alignment requirements, and size limits for the RVF format.
/// Segment header magic: "RVFS" in ASCII (little-endian u32).
pub const SEGMENT_MAGIC: u32 = 0x5256_4653;
/// Root manifest magic: "RVM0" in ASCII (little-endian u32).
pub const ROOT_MANIFEST_MAGIC: u32 = 0x5256_4D30;
/// All segments must start at a 64-byte aligned boundary (AVX-512 / cache-line width).
pub const SEGMENT_ALIGNMENT: usize = 64;
/// The Level 0 root manifest is always exactly 4096 bytes (one OS page / disk sector).
pub const ROOT_MANIFEST_SIZE: usize = 4096;
/// Maximum payload size for a single segment (4 GiB).
pub const MAX_SEGMENT_PAYLOAD: u64 = 4 * 1024 * 1024 * 1024;
/// Size of the segment header in bytes.
pub const SEGMENT_HEADER_SIZE: usize = 64;
/// Current segment format version.
pub const SEGMENT_VERSION: u8 = 1;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn magic_bytes_match_ascii() {
// "RVFS" => 0x52 0x56 0x46 0x53
let bytes = SEGMENT_MAGIC.to_le_bytes();
assert_eq!(&bytes, b"SFVR"); // LE representation: 0x53, 0x46, 0x56, 0x52
let bytes_be = SEGMENT_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVFS");
}
#[test]
fn root_manifest_magic_bytes() {
let bytes_be = ROOT_MANIFEST_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVM0");
}
#[test]
fn alignment_is_power_of_two() {
assert!(SEGMENT_ALIGNMENT.is_power_of_two());
}
#[test]
fn max_payload_is_4gb() {
assert_eq!(MAX_SEGMENT_PAYLOAD, 0x1_0000_0000);
}
}

View File

@@ -0,0 +1,243 @@
//! COW_MAP_SEG (0x20) types for the RVF computational container.
//!
//! Defines the 64-byte `CowMapHeader` and associated enums per ADR-031.
//! The COW_MAP_SEG tracks copy-on-write cluster mappings, enabling
//! branching and snapshotting of vector data without full duplication.
use crate::error::RvfError;
/// Magic number for `CowMapHeader`: "RVCM" in big-endian.
pub const COWMAP_MAGIC: u32 = 0x5256_434D;
/// Cluster map storage format.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum MapFormat {
/// Simple flat array of cluster entries.
FlatArray = 0,
/// Adaptive Radix Tree for sparse mappings.
ArtTree = 1,
/// Extent list for contiguous ranges.
ExtentList = 2,
}
impl TryFrom<u8> for MapFormat {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::FlatArray),
1 => Ok(Self::ArtTree),
2 => Ok(Self::ExtentList),
_ => Err(RvfError::InvalidEnumValue {
type_name: "MapFormat",
value: value as u64,
}),
}
}
}
/// Entry in the COW cluster map.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CowMapEntry {
/// Cluster has been written locally at the given offset.
LocalOffset(u64),
/// Cluster data lives in the parent file.
ParentRef,
/// Cluster has not been allocated.
Unallocated,
}
/// 64-byte header for COW_MAP_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct CowMapHeader {
/// Magic: `COWMAP_MAGIC` (0x5256434D, "RVCM").
pub magic: u32,
/// CowMapHeader format version (currently 1).
pub version: u16,
/// Map storage format (see `MapFormat`).
pub map_format: u8,
/// Compression policy for COW clusters.
pub compression_policy: u8,
/// Cluster size in bytes (must be power of 2, SIMD aligned).
pub cluster_size_bytes: u32,
/// Number of vectors per cluster.
pub vectors_per_cluster: u32,
/// UUID of the base (parent) file.
pub base_file_id: [u8; 16],
/// SHAKE-256-256 hash of the base file.
pub base_file_hash: [u8; 32],
}
// Compile-time assertion: CowMapHeader must be exactly 64 bytes.
const _: () = assert!(core::mem::size_of::<CowMapHeader>() == 64);
impl CowMapHeader {
/// Serialize the header to a 64-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
buf[0x00..0x04].copy_from_slice(&self.magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.version.to_le_bytes());
buf[0x06] = self.map_format;
buf[0x07] = self.compression_policy;
buf[0x08..0x0C].copy_from_slice(&self.cluster_size_bytes.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&self.vectors_per_cluster.to_le_bytes());
buf[0x10..0x20].copy_from_slice(&self.base_file_id);
buf[0x20..0x40].copy_from_slice(&self.base_file_hash);
buf
}
/// Deserialize a `CowMapHeader` from a 64-byte slice.
pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != COWMAP_MAGIC {
return Err(RvfError::BadMagic {
expected: COWMAP_MAGIC,
got: magic,
});
}
let version = u16::from_le_bytes([data[0x04], data[0x05]]);
let map_format = data[0x06];
let cluster_size_bytes =
u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]);
let vectors_per_cluster =
u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]);
// Validate map_format is a known enum value
let _ = MapFormat::try_from(map_format)?;
// Validate cluster_size_bytes is a power of 2 and non-zero
if cluster_size_bytes == 0 || !cluster_size_bytes.is_power_of_two() {
return Err(RvfError::InvalidEnumValue {
type_name: "CowMapHeader::cluster_size_bytes",
value: cluster_size_bytes as u64,
});
}
// Validate vectors_per_cluster is non-zero (prevents division by zero)
if vectors_per_cluster == 0 {
return Err(RvfError::InvalidEnumValue {
type_name: "CowMapHeader::vectors_per_cluster",
value: 0,
});
}
Ok(Self {
magic,
version,
map_format,
compression_policy: data[0x07],
cluster_size_bytes,
vectors_per_cluster,
base_file_id: {
let mut id = [0u8; 16];
id.copy_from_slice(&data[0x10..0x20]);
id
},
base_file_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x20..0x40]);
h
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> CowMapHeader {
CowMapHeader {
magic: COWMAP_MAGIC,
version: 1,
map_format: MapFormat::FlatArray as u8,
compression_policy: 0,
cluster_size_bytes: 4096,
vectors_per_cluster: 64,
base_file_id: [0xAA; 16],
base_file_hash: [0xBB; 32],
}
}
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<CowMapHeader>(), 64);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = COWMAP_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVCM");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = CowMapHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.magic, COWMAP_MAGIC);
assert_eq!(decoded.version, 1);
assert_eq!(decoded.map_format, MapFormat::FlatArray as u8);
assert_eq!(decoded.compression_policy, 0);
assert_eq!(decoded.cluster_size_bytes, 4096);
assert_eq!(decoded.vectors_per_cluster, 64);
assert_eq!(decoded.base_file_id, [0xAA; 16]);
assert_eq!(decoded.base_file_hash, [0xBB; 32]);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = CowMapHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, COWMAP_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.magic as *const _ as usize - base, 0x00);
assert_eq!(&h.version as *const _ as usize - base, 0x04);
assert_eq!(&h.map_format as *const _ as usize - base, 0x06);
assert_eq!(&h.compression_policy as *const _ as usize - base, 0x07);
assert_eq!(&h.cluster_size_bytes as *const _ as usize - base, 0x08);
assert_eq!(&h.vectors_per_cluster as *const _ as usize - base, 0x0C);
assert_eq!(&h.base_file_id as *const _ as usize - base, 0x10);
assert_eq!(&h.base_file_hash as *const _ as usize - base, 0x20);
}
#[test]
fn map_format_try_from() {
assert_eq!(MapFormat::try_from(0), Ok(MapFormat::FlatArray));
assert_eq!(MapFormat::try_from(1), Ok(MapFormat::ArtTree));
assert_eq!(MapFormat::try_from(2), Ok(MapFormat::ExtentList));
assert!(MapFormat::try_from(3).is_err());
assert!(MapFormat::try_from(0xFF).is_err());
}
#[test]
fn cow_map_entry_variants() {
let local = CowMapEntry::LocalOffset(0x1000);
let parent = CowMapEntry::ParentRef;
let unalloc = CowMapEntry::Unallocated;
assert_eq!(local, CowMapEntry::LocalOffset(0x1000));
assert_eq!(parent, CowMapEntry::ParentRef);
assert_eq!(unalloc, CowMapEntry::Unallocated);
assert_ne!(local, parent);
}
}

View File

@@ -0,0 +1,191 @@
//! DASHBOARD_SEG (0x11) types for the RVF computational container.
//!
//! Defines the 64-byte `DashboardHeader` and associated constants per ADR-040.
//! The DASHBOARD_SEG embeds a pre-built web dashboard (e.g. Vite + Three.js)
//! that the RVF HTTP server can serve at `/`.
use crate::error::RvfError;
/// Magic number for `DashboardHeader`: "RVDB" in big-endian.
pub const DASHBOARD_MAGIC: u32 = 0x5256_4442;
/// Maximum dashboard bundle size (64 MiB).
pub const DASHBOARD_MAX_SIZE: u64 = 64 * 1024 * 1024;
/// 64-byte header for DASHBOARD_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire.
///
/// Payload layout after header:
/// `[entry_path_bytes | file_table | file_data...]`
///
/// File table: array of `(path_len: u16, data_offset: u64, data_size: u64, path_bytes: [u8])`
/// File data: concatenated raw file contents.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct DashboardHeader {
/// Magic: `DASHBOARD_MAGIC` (0x52564442, "RVDB").
pub dashboard_magic: u32,
/// DashboardHeader format version (currently 1).
pub header_version: u16,
/// UI framework: 0=threejs, 1=react, 2=custom.
pub ui_framework: u8,
/// Compression: 0=none, 1=gzip, 2=brotli.
pub compression: u8,
/// Total uncompressed bundle size in bytes.
pub bundle_size: u64,
/// Number of files in the bundle.
pub file_count: u32,
/// Length of the entry point path string.
pub entry_path_len: u16,
/// Reserved padding.
pub reserved: u16,
/// Build timestamp (unix epoch seconds).
pub build_timestamp: u64,
/// SHAKE-256-256 of the entire bundle payload.
pub content_hash: [u8; 32],
}
// Compile-time assertion: DashboardHeader must be exactly 64 bytes.
const _: () = assert!(core::mem::size_of::<DashboardHeader>() == 64);
impl DashboardHeader {
/// Serialize the header to a 64-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
buf[0x00..0x04].copy_from_slice(&self.dashboard_magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.header_version.to_le_bytes());
buf[0x06] = self.ui_framework;
buf[0x07] = self.compression;
buf[0x08..0x10].copy_from_slice(&self.bundle_size.to_le_bytes());
buf[0x10..0x14].copy_from_slice(&self.file_count.to_le_bytes());
buf[0x14..0x16].copy_from_slice(&self.entry_path_len.to_le_bytes());
buf[0x16..0x18].copy_from_slice(&self.reserved.to_le_bytes());
buf[0x18..0x20].copy_from_slice(&self.build_timestamp.to_le_bytes());
buf[0x20..0x40].copy_from_slice(&self.content_hash);
buf
}
/// Deserialize a `DashboardHeader` from a 64-byte slice.
pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != DASHBOARD_MAGIC {
return Err(RvfError::BadMagic {
expected: DASHBOARD_MAGIC,
got: magic,
});
}
Ok(Self {
dashboard_magic: magic,
header_version: u16::from_le_bytes([data[0x04], data[0x05]]),
ui_framework: data[0x06],
compression: data[0x07],
bundle_size: u64::from_le_bytes([
data[0x08], data[0x09], data[0x0A], data[0x0B], data[0x0C], data[0x0D], data[0x0E],
data[0x0F],
]),
file_count: u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]),
entry_path_len: u16::from_le_bytes([data[0x14], data[0x15]]),
reserved: u16::from_le_bytes([data[0x16], data[0x17]]),
build_timestamp: u64::from_le_bytes([
data[0x18], data[0x19], data[0x1A], data[0x1B], data[0x1C], data[0x1D], data[0x1E],
data[0x1F],
]),
content_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x20..0x40]);
h
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> DashboardHeader {
DashboardHeader {
dashboard_magic: DASHBOARD_MAGIC,
header_version: 1,
ui_framework: 0, // threejs
compression: 0, // none
bundle_size: 524288,
file_count: 12,
entry_path_len: 10,
reserved: 0,
build_timestamp: 1_700_000_000,
content_hash: [0xAB; 32],
}
}
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<DashboardHeader>(), 64);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = DASHBOARD_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVDB");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = DashboardHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.dashboard_magic, DASHBOARD_MAGIC);
assert_eq!(decoded.header_version, 1);
assert_eq!(decoded.ui_framework, 0);
assert_eq!(decoded.compression, 0);
assert_eq!(decoded.bundle_size, 524288);
assert_eq!(decoded.file_count, 12);
assert_eq!(decoded.entry_path_len, 10);
assert_eq!(decoded.reserved, 0);
assert_eq!(decoded.build_timestamp, 1_700_000_000);
assert_eq!(decoded.content_hash, [0xAB; 32]);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = DashboardHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, DASHBOARD_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.dashboard_magic as *const _ as usize - base, 0x00);
assert_eq!(&h.header_version as *const _ as usize - base, 0x04);
assert_eq!(&h.ui_framework as *const _ as usize - base, 0x06);
assert_eq!(&h.compression as *const _ as usize - base, 0x07);
assert_eq!(&h.bundle_size as *const _ as usize - base, 0x08);
assert_eq!(&h.file_count as *const _ as usize - base, 0x10);
assert_eq!(&h.entry_path_len as *const _ as usize - base, 0x14);
assert_eq!(&h.reserved as *const _ as usize - base, 0x16);
assert_eq!(&h.build_timestamp as *const _ as usize - base, 0x18);
assert_eq!(&h.content_hash as *const _ as usize - base, 0x20);
}
#[test]
fn large_bundle_size_round_trip() {
let mut h = sample_header();
h.bundle_size = DASHBOARD_MAX_SIZE;
h.file_count = 500;
let bytes = h.to_bytes();
let decoded = DashboardHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.bundle_size, DASHBOARD_MAX_SIZE);
assert_eq!(decoded.file_count, 500);
}
}

View File

@@ -0,0 +1,89 @@
//! Vector data type discriminator.
/// Identifies the numeric encoding of vector elements.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum DataType {
/// 32-bit IEEE 754 float.
F32 = 0,
/// 16-bit IEEE 754 half-precision float.
F16 = 1,
/// Brain floating point (bfloat16).
BF16 = 2,
/// Signed 8-bit integer (scalar quantized).
I8 = 3,
/// Unsigned 8-bit integer.
U8 = 4,
/// 4-bit integer (packed, 2 per byte).
I4 = 5,
/// 1-bit binary (packed, 8 per byte).
Binary = 6,
/// Product-quantized codes.
PQ = 7,
/// Custom encoding (see QUANT_SEG for details).
Custom = 8,
}
impl DataType {
/// Returns the number of bits per element, or `None` for variable-width types.
pub const fn bits_per_element(self) -> Option<u32> {
match self {
Self::F32 => Some(32),
Self::F16 => Some(16),
Self::BF16 => Some(16),
Self::I8 => Some(8),
Self::U8 => Some(8),
Self::I4 => Some(4),
Self::Binary => Some(1),
Self::PQ | Self::Custom => None,
}
}
}
impl TryFrom<u8> for DataType {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::F32),
1 => Ok(Self::F16),
2 => Ok(Self::BF16),
3 => Ok(Self::I8),
4 => Ok(Self::U8),
5 => Ok(Self::I4),
6 => Ok(Self::Binary),
7 => Ok(Self::PQ),
8 => Ok(Self::Custom),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
for raw in 0..=8u8 {
let dt = DataType::try_from(raw).unwrap();
assert_eq!(dt as u8, raw);
}
}
#[test]
fn invalid_value() {
assert_eq!(DataType::try_from(9), Err(9));
assert_eq!(DataType::try_from(255), Err(255));
}
#[test]
fn bits_per_element() {
assert_eq!(DataType::F32.bits_per_element(), Some(32));
assert_eq!(DataType::F16.bits_per_element(), Some(16));
assert_eq!(DataType::I4.bits_per_element(), Some(4));
assert_eq!(DataType::Binary.bits_per_element(), Some(1));
assert_eq!(DataType::PQ.bits_per_element(), None);
}
}

View File

@@ -0,0 +1,203 @@
//! DELTA_SEG (0x23) types for the RVF computational container.
//!
//! Defines the 64-byte `DeltaHeader` and associated enums per ADR-031.
//! The DELTA_SEG stores sparse delta patches between clusters,
//! enabling efficient incremental updates without full cluster rewrites.
use crate::error::RvfError;
/// Magic number for `DeltaHeader`: "RVDL" in big-endian.
pub const DELTA_MAGIC: u32 = 0x5256_444C;
/// Delta encoding strategy.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum DeltaEncoding {
/// Sparse row patches (individual vector updates).
SparseRows = 0,
/// Low-rank approximation of the delta.
LowRank = 1,
/// Full cluster patch (complete replacement).
FullPatch = 2,
}
impl TryFrom<u8> for DeltaEncoding {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::SparseRows),
1 => Ok(Self::LowRank),
2 => Ok(Self::FullPatch),
_ => Err(RvfError::InvalidEnumValue {
type_name: "DeltaEncoding",
value: value as u64,
}),
}
}
}
/// 64-byte header for DELTA_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct DeltaHeader {
/// Magic: `DELTA_MAGIC` (0x5256444C, "RVDL").
pub magic: u32,
/// DeltaHeader format version (currently 1).
pub version: u16,
/// Delta encoding strategy (see `DeltaEncoding`).
pub encoding: u8,
/// Padding (must be zero).
pub _pad: u8,
/// Cluster ID that this delta applies to.
pub base_cluster_id: u32,
/// Number of vectors affected by this delta.
pub affected_count: u32,
/// Size of the delta payload in bytes.
pub delta_size: u64,
/// SHAKE-256-256 hash of the delta payload.
pub delta_hash: [u8; 32],
/// Reserved (must be zero).
pub _reserved: [u8; 8],
}
// Compile-time assertion: DeltaHeader must be exactly 64 bytes.
const _: () = assert!(core::mem::size_of::<DeltaHeader>() == 64);
impl DeltaHeader {
/// Serialize the header to a 64-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
buf[0x00..0x04].copy_from_slice(&self.magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.version.to_le_bytes());
buf[0x06] = self.encoding;
buf[0x07] = self._pad;
buf[0x08..0x0C].copy_from_slice(&self.base_cluster_id.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&self.affected_count.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&self.delta_size.to_le_bytes());
buf[0x18..0x38].copy_from_slice(&self.delta_hash);
buf[0x38..0x40].copy_from_slice(&self._reserved);
buf
}
/// Deserialize a `DeltaHeader` from a 64-byte slice.
pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != DELTA_MAGIC {
return Err(RvfError::BadMagic {
expected: DELTA_MAGIC,
got: magic,
});
}
Ok(Self {
magic,
version: u16::from_le_bytes([data[0x04], data[0x05]]),
encoding: data[0x06],
_pad: data[0x07],
base_cluster_id: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
affected_count: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
delta_size: u64::from_le_bytes([
data[0x10], data[0x11], data[0x12], data[0x13], data[0x14], data[0x15], data[0x16],
data[0x17],
]),
delta_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x18..0x38]);
h
},
_reserved: {
let mut r = [0u8; 8];
r.copy_from_slice(&data[0x38..0x40]);
r
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> DeltaHeader {
DeltaHeader {
magic: DELTA_MAGIC,
version: 1,
encoding: DeltaEncoding::SparseRows as u8,
_pad: 0,
base_cluster_id: 42,
affected_count: 10,
delta_size: 2048,
delta_hash: [0xDD; 32],
_reserved: [0; 8],
}
}
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<DeltaHeader>(), 64);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = DELTA_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVDL");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = DeltaHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.magic, DELTA_MAGIC);
assert_eq!(decoded.version, 1);
assert_eq!(decoded.encoding, DeltaEncoding::SparseRows as u8);
assert_eq!(decoded._pad, 0);
assert_eq!(decoded.base_cluster_id, 42);
assert_eq!(decoded.affected_count, 10);
assert_eq!(decoded.delta_size, 2048);
assert_eq!(decoded.delta_hash, [0xDD; 32]);
assert_eq!(decoded._reserved, [0; 8]);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = DeltaHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, DELTA_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.magic as *const _ as usize - base, 0x00);
assert_eq!(&h.version as *const _ as usize - base, 0x04);
assert_eq!(&h.encoding as *const _ as usize - base, 0x06);
assert_eq!(&h._pad as *const _ as usize - base, 0x07);
assert_eq!(&h.base_cluster_id as *const _ as usize - base, 0x08);
assert_eq!(&h.affected_count as *const _ as usize - base, 0x0C);
assert_eq!(&h.delta_size as *const _ as usize - base, 0x10);
assert_eq!(&h.delta_hash as *const _ as usize - base, 0x18);
assert_eq!(&h._reserved as *const _ as usize - base, 0x38);
}
#[test]
fn delta_encoding_try_from() {
assert_eq!(DeltaEncoding::try_from(0), Ok(DeltaEncoding::SparseRows));
assert_eq!(DeltaEncoding::try_from(1), Ok(DeltaEncoding::LowRank));
assert_eq!(DeltaEncoding::try_from(2), Ok(DeltaEncoding::FullPatch));
assert!(DeltaEncoding::try_from(3).is_err());
assert!(DeltaEncoding::try_from(0xFF).is_err());
}
}

View File

@@ -0,0 +1,333 @@
//! EBPF_SEG (0x0F) types for the RVF computational container.
//!
//! Defines the 64-byte `EbpfHeader` and associated enums per ADR-030.
//! The EBPF_SEG embeds an eBPF program for kernel-level fast-path
//! vector distance computation (L0 cache in BPF maps).
use crate::error::RvfError;
/// Magic number for `EbpfHeader`: "RVBP" in big-endian.
pub const EBPF_MAGIC: u32 = 0x5256_4250;
/// eBPF program type classification.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum EbpfProgramType {
/// XDP program for distance computation on packets.
XdpDistance = 0x00,
/// TC classifier for query routing.
TcFilter = 0x01,
/// Socket filter for query preprocessing.
SocketFilter = 0x02,
/// Tracepoint for performance monitoring.
Tracepoint = 0x03,
/// Kprobe for dynamic instrumentation.
Kprobe = 0x04,
/// Cgroup socket buffer filter.
CgroupSkb = 0x05,
/// Custom program type.
Custom = 0xFF,
}
impl TryFrom<u8> for EbpfProgramType {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::XdpDistance),
0x01 => Ok(Self::TcFilter),
0x02 => Ok(Self::SocketFilter),
0x03 => Ok(Self::Tracepoint),
0x04 => Ok(Self::Kprobe),
0x05 => Ok(Self::CgroupSkb),
0xFF => Ok(Self::Custom),
_ => Err(RvfError::InvalidEnumValue {
type_name: "EbpfProgramType",
value: value as u64,
}),
}
}
}
/// eBPF attach point classification.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum EbpfAttachType {
/// XDP hook on NIC ingress.
XdpIngress = 0x00,
/// TC ingress qdisc.
TcIngress = 0x01,
/// TC egress qdisc.
TcEgress = 0x02,
/// Socket filter attachment.
SocketFilter = 0x03,
/// Cgroup ingress.
CgroupIngress = 0x04,
/// Cgroup egress.
CgroupEgress = 0x05,
/// No automatic attachment.
None = 0xFF,
}
impl TryFrom<u8> for EbpfAttachType {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::XdpIngress),
0x01 => Ok(Self::TcIngress),
0x02 => Ok(Self::TcEgress),
0x03 => Ok(Self::SocketFilter),
0x04 => Ok(Self::CgroupIngress),
0x05 => Ok(Self::CgroupEgress),
0xFF => Ok(Self::None),
_ => Err(RvfError::InvalidEnumValue {
type_name: "EbpfAttachType",
value: value as u64,
}),
}
}
}
/// 64-byte header for EBPF_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct EbpfHeader {
/// Magic: `EBPF_MAGIC` (0x52564250, "RVBP").
pub ebpf_magic: u32,
/// EbpfHeader format version (currently 1).
pub header_version: u16,
/// eBPF program type (see `EbpfProgramType`).
pub program_type: u8,
/// eBPF attach point (see `EbpfAttachType`).
pub attach_type: u8,
/// Bitfield flags for the eBPF program.
pub program_flags: u32,
/// Number of BPF instructions (max 65535).
pub insn_count: u16,
/// Maximum vector dimension this program handles.
pub max_dimension: u16,
/// ELF object size (bytes).
pub program_size: u64,
/// Number of BPF maps defined.
pub map_count: u32,
/// BTF (BPF Type Format) section size.
pub btf_size: u32,
/// SHAKE-256-256 of the ELF object.
pub program_hash: [u8; 32],
}
// Compile-time assertion: EbpfHeader must be exactly 64 bytes.
const _: () = assert!(core::mem::size_of::<EbpfHeader>() == 64);
impl EbpfHeader {
/// Serialize the header to a 64-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
buf[0x00..0x04].copy_from_slice(&self.ebpf_magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.header_version.to_le_bytes());
buf[0x06] = self.program_type;
buf[0x07] = self.attach_type;
buf[0x08..0x0C].copy_from_slice(&self.program_flags.to_le_bytes());
buf[0x0C..0x0E].copy_from_slice(&self.insn_count.to_le_bytes());
buf[0x0E..0x10].copy_from_slice(&self.max_dimension.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&self.program_size.to_le_bytes());
buf[0x18..0x1C].copy_from_slice(&self.map_count.to_le_bytes());
buf[0x1C..0x20].copy_from_slice(&self.btf_size.to_le_bytes());
buf[0x20..0x40].copy_from_slice(&self.program_hash);
buf
}
/// Deserialize an `EbpfHeader` from a 64-byte slice.
pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != EBPF_MAGIC {
return Err(RvfError::BadMagic {
expected: EBPF_MAGIC,
got: magic,
});
}
Ok(Self {
ebpf_magic: magic,
header_version: u16::from_le_bytes([data[0x04], data[0x05]]),
program_type: data[0x06],
attach_type: data[0x07],
program_flags: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
insn_count: u16::from_le_bytes([data[0x0C], data[0x0D]]),
max_dimension: u16::from_le_bytes([data[0x0E], data[0x0F]]),
program_size: u64::from_le_bytes([
data[0x10], data[0x11], data[0x12], data[0x13], data[0x14], data[0x15], data[0x16],
data[0x17],
]),
map_count: u32::from_le_bytes([data[0x18], data[0x19], data[0x1A], data[0x1B]]),
btf_size: u32::from_le_bytes([data[0x1C], data[0x1D], data[0x1E], data[0x1F]]),
program_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x20..0x40]);
h
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> EbpfHeader {
EbpfHeader {
ebpf_magic: EBPF_MAGIC,
header_version: 1,
program_type: EbpfProgramType::XdpDistance as u8,
attach_type: EbpfAttachType::XdpIngress as u8,
program_flags: 0,
insn_count: 256,
max_dimension: 1536,
program_size: 4096,
map_count: 2,
btf_size: 512,
program_hash: [0xDE; 32],
}
}
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<EbpfHeader>(), 64);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = EBPF_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVBP");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = EbpfHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.ebpf_magic, EBPF_MAGIC);
assert_eq!(decoded.header_version, 1);
assert_eq!(decoded.program_type, EbpfProgramType::XdpDistance as u8);
assert_eq!(decoded.attach_type, EbpfAttachType::XdpIngress as u8);
assert_eq!(decoded.program_flags, 0);
assert_eq!(decoded.insn_count, 256);
assert_eq!(decoded.max_dimension, 1536);
assert_eq!(decoded.program_size, 4096);
assert_eq!(decoded.map_count, 2);
assert_eq!(decoded.btf_size, 512);
assert_eq!(decoded.program_hash, [0xDE; 32]);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = EbpfHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, EBPF_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.ebpf_magic as *const _ as usize - base, 0x00);
assert_eq!(&h.header_version as *const _ as usize - base, 0x04);
assert_eq!(&h.program_type as *const _ as usize - base, 0x06);
assert_eq!(&h.attach_type as *const _ as usize - base, 0x07);
assert_eq!(&h.program_flags as *const _ as usize - base, 0x08);
assert_eq!(&h.insn_count as *const _ as usize - base, 0x0C);
assert_eq!(&h.max_dimension as *const _ as usize - base, 0x0E);
assert_eq!(&h.program_size as *const _ as usize - base, 0x10);
assert_eq!(&h.map_count as *const _ as usize - base, 0x18);
assert_eq!(&h.btf_size as *const _ as usize - base, 0x1C);
assert_eq!(&h.program_hash as *const _ as usize - base, 0x20);
}
#[test]
fn ebpf_program_type_try_from() {
assert_eq!(
EbpfProgramType::try_from(0x00),
Ok(EbpfProgramType::XdpDistance)
);
assert_eq!(
EbpfProgramType::try_from(0x01),
Ok(EbpfProgramType::TcFilter)
);
assert_eq!(
EbpfProgramType::try_from(0x02),
Ok(EbpfProgramType::SocketFilter)
);
assert_eq!(
EbpfProgramType::try_from(0x03),
Ok(EbpfProgramType::Tracepoint)
);
assert_eq!(EbpfProgramType::try_from(0x04), Ok(EbpfProgramType::Kprobe));
assert_eq!(
EbpfProgramType::try_from(0x05),
Ok(EbpfProgramType::CgroupSkb)
);
assert_eq!(EbpfProgramType::try_from(0xFF), Ok(EbpfProgramType::Custom));
assert!(EbpfProgramType::try_from(0x06).is_err());
assert!(EbpfProgramType::try_from(0x80).is_err());
}
#[test]
fn ebpf_attach_type_try_from() {
assert_eq!(
EbpfAttachType::try_from(0x00),
Ok(EbpfAttachType::XdpIngress)
);
assert_eq!(
EbpfAttachType::try_from(0x01),
Ok(EbpfAttachType::TcIngress)
);
assert_eq!(EbpfAttachType::try_from(0x02), Ok(EbpfAttachType::TcEgress));
assert_eq!(
EbpfAttachType::try_from(0x03),
Ok(EbpfAttachType::SocketFilter)
);
assert_eq!(
EbpfAttachType::try_from(0x04),
Ok(EbpfAttachType::CgroupIngress)
);
assert_eq!(
EbpfAttachType::try_from(0x05),
Ok(EbpfAttachType::CgroupEgress)
);
assert_eq!(EbpfAttachType::try_from(0xFF), Ok(EbpfAttachType::None));
assert!(EbpfAttachType::try_from(0x06).is_err());
assert!(EbpfAttachType::try_from(0x80).is_err());
}
#[test]
fn max_dimension_round_trip() {
let mut h = sample_header();
h.max_dimension = 2048;
let bytes = h.to_bytes();
let decoded = EbpfHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.max_dimension, 2048);
}
#[test]
fn large_program_size_round_trip() {
let mut h = sample_header();
h.program_size = 1_048_576; // 1 MiB
h.insn_count = 65535;
let bytes = h.to_bytes();
let decoded = EbpfHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.program_size, 1_048_576);
assert_eq!(decoded.insn_count, 65535);
}
}

View File

@@ -0,0 +1,269 @@
//! Ed25519 asymmetric signing (RFC 8032).
//!
//! Provides keypair generation, signing, and verification using the
//! `ed25519-dalek` crate. Feature-gated behind the `ed25519` feature.
use ed25519_dalek::{Signature as DalekSignature, Signer, SigningKey, Verifier, VerifyingKey};
/// Ed25519 public key size in bytes.
pub const PUBLIC_KEY_SIZE: usize = 32;
/// Ed25519 secret (signing) key size in bytes.
pub const SECRET_KEY_SIZE: usize = 32;
/// Ed25519 signature size in bytes.
pub const SIGNATURE_SIZE: usize = 64;
// Compile-time size assertions (mirrors sha256.rs pattern).
const _: () = assert!(PUBLIC_KEY_SIZE == 32);
const _: () = assert!(SECRET_KEY_SIZE == 32);
const _: () = assert!(SIGNATURE_SIZE == 64);
/// An Ed25519 keypair (signing key + verifying key).
///
/// The signing key is 32 bytes of secret material; the verifying key
/// is the corresponding 32-byte public point on the Ed25519 curve.
#[derive(Clone)]
pub struct Ed25519Keypair {
signing: SigningKey,
}
impl Ed25519Keypair {
/// Generate a new random keypair from the provided RNG.
pub fn generate<R: rand_core::CryptoRngCore>(rng: &mut R) -> Self {
Self {
signing: SigningKey::generate(rng),
}
}
/// Reconstruct a keypair from a 32-byte secret key.
pub fn from_secret(secret: &[u8; SECRET_KEY_SIZE]) -> Self {
Self {
signing: SigningKey::from_bytes(secret),
}
}
/// Return the 32-byte secret (signing) key.
pub fn secret_key(&self) -> [u8; SECRET_KEY_SIZE] {
self.signing.to_bytes()
}
/// Return the 32-byte public (verifying) key.
pub fn public_key(&self) -> [u8; PUBLIC_KEY_SIZE] {
self.signing.verifying_key().to_bytes()
}
}
/// Sign `message` with an Ed25519 secret key. Returns a 64-byte signature.
///
/// This is a deterministic operation: the same key + message always
/// produces the same signature (per RFC 8032).
pub fn ed25519_sign(secret: &[u8; SECRET_KEY_SIZE], message: &[u8]) -> [u8; SIGNATURE_SIZE] {
let signing_key = SigningKey::from_bytes(secret);
let sig: DalekSignature = signing_key.sign(message);
sig.to_bytes()
}
/// Verify an Ed25519 signature against a public key and message.
///
/// Returns `true` if the signature is valid, `false` otherwise.
pub fn ed25519_verify(
public: &[u8; PUBLIC_KEY_SIZE],
message: &[u8],
signature: &[u8; SIGNATURE_SIZE],
) -> bool {
let Ok(verifying_key) = VerifyingKey::from_bytes(public) else {
return false;
};
let sig = DalekSignature::from_bytes(signature);
verifying_key.verify(message, &sig).is_ok()
}
/// Constant-time comparison of two 64-byte signatures.
pub fn ct_eq_sig(a: &[u8; SIGNATURE_SIZE], b: &[u8; SIGNATURE_SIZE]) -> bool {
let mut diff = 0u8;
for i in 0..SIGNATURE_SIZE {
diff |= a[i] ^ b[i];
}
diff == 0
}
#[cfg(test)]
mod tests {
use super::*;
/// Deterministic RNG for reproducible tests.
struct TestRng(u64);
impl rand_core::RngCore for TestRng {
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
fn next_u64(&mut self) -> u64 {
// Simple xorshift64 for test determinism.
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut i = 0;
while i < dest.len() {
let val = self.next_u64().to_le_bytes();
let remaining = dest.len() - i;
let take = if remaining < 8 { remaining } else { 8 };
dest[i..i + take].copy_from_slice(&val[..take]);
i += take;
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl rand_core::CryptoRng for TestRng {}
fn test_rng() -> TestRng {
TestRng(0xDEAD_BEEF_CAFE_1234)
}
// --- Test 1: keypair generation ---
#[test]
fn keygen_produces_valid_keypair() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let secret = kp.secret_key();
let public = kp.public_key();
assert_eq!(secret.len(), SECRET_KEY_SIZE);
assert_eq!(public.len(), PUBLIC_KEY_SIZE);
// Keys should not be all zeros.
assert_ne!(secret, [0u8; SECRET_KEY_SIZE]);
assert_ne!(public, [0u8; PUBLIC_KEY_SIZE]);
}
// --- Test 2: sign produces a signature ---
#[test]
fn sign_returns_64_byte_signature() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let message = b"hello RVF";
let sig = ed25519_sign(&kp.secret_key(), message);
assert_eq!(sig.len(), SIGNATURE_SIZE);
assert_ne!(sig, [0u8; SIGNATURE_SIZE]);
}
// --- Test 3: sign then verify round-trip ---
#[test]
fn sign_verify_round_trip() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let message = b"The quick brown fox jumps over the lazy dog";
let sig = ed25519_sign(&kp.secret_key(), message);
assert!(ed25519_verify(&kp.public_key(), message, &sig));
}
// --- Test 4: wrong key rejects ---
#[test]
fn wrong_key_rejects() {
let mut rng = test_rng();
let kp1 = Ed25519Keypair::generate(&mut rng);
let kp2 = Ed25519Keypair::generate(&mut rng);
let message = b"signed by kp1";
let sig = ed25519_sign(&kp1.secret_key(), message);
// Verify with kp2's public key should fail.
assert!(!ed25519_verify(&kp2.public_key(), message, &sig));
}
// --- Test 5: tampered message rejects ---
#[test]
fn tampered_message_rejects() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let message = b"original payload";
let sig = ed25519_sign(&kp.secret_key(), message);
assert!(!ed25519_verify(&kp.public_key(), b"tampered payload", &sig));
}
// --- Test 6: deterministic signatures ---
#[test]
fn deterministic_signatures() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let message = b"determinism test";
let sig1 = ed25519_sign(&kp.secret_key(), message);
let sig2 = ed25519_sign(&kp.secret_key(), message);
assert_eq!(sig1, sig2);
}
// --- Test 7: different messages produce different signatures ---
#[test]
fn different_messages_different_sigs() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let sig_a = ed25519_sign(&kp.secret_key(), b"message A");
let sig_b = ed25519_sign(&kp.secret_key(), b"message B");
assert_ne!(sig_a, sig_b);
}
// --- Test 8: empty message ---
#[test]
fn empty_message_sign_verify() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let message = b"";
let sig = ed25519_sign(&kp.secret_key(), message);
assert!(ed25519_verify(&kp.public_key(), message, &sig));
}
// --- Additional tests ---
#[test]
fn from_secret_round_trip() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let secret = kp.secret_key();
let restored = Ed25519Keypair::from_secret(&secret);
assert_eq!(kp.public_key(), restored.public_key());
assert_eq!(kp.secret_key(), restored.secret_key());
}
#[test]
fn ct_eq_sig_same() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let sig = ed25519_sign(&kp.secret_key(), b"test");
assert!(ct_eq_sig(&sig, &sig));
}
#[test]
fn ct_eq_sig_different() {
let mut rng = test_rng();
let kp = Ed25519Keypair::generate(&mut rng);
let sig1 = ed25519_sign(&kp.secret_key(), b"msg1");
let sig2 = ed25519_sign(&kp.secret_key(), b"msg2");
assert!(!ct_eq_sig(&sig1, &sig2));
}
}

View File

@@ -0,0 +1,453 @@
//! Error codes and error types for the RVF format.
//!
//! Error codes are 16-bit unsigned integers where the high byte identifies
//! the category and the low byte the specific error.
/// Wire-format error code (u16). The high byte is the category, the low byte is
/// the specific error within that category.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
pub enum ErrorCode {
// ---- Category 0x00: Success ----
/// Operation succeeded.
Ok = 0x0000,
/// Partial success (some items failed).
OkPartial = 0x0001,
// ---- Category 0x01: Format Errors ----
/// Segment magic mismatch (expected 0x52564653).
InvalidMagic = 0x0100,
/// Unsupported segment version.
InvalidVersion = 0x0101,
/// Segment hash verification failed.
InvalidChecksum = 0x0102,
/// Cryptographic signature invalid.
InvalidSignature = 0x0103,
/// Segment payload shorter than declared length.
TruncatedSegment = 0x0104,
/// Root manifest validation failed.
InvalidManifest = 0x0105,
/// No valid MANIFEST_SEG in file.
ManifestNotFound = 0x0106,
/// Segment type not recognized (advisory, not fatal).
UnknownSegmentType = 0x0107,
/// Data not at expected 64-byte boundary.
AlignmentError = 0x0108,
// ---- Category 0x02: Query Errors ----
/// Query vector dimension != index dimension.
DimensionMismatch = 0x0200,
/// No index segments available.
EmptyIndex = 0x0201,
/// Requested distance metric not available.
MetricUnsupported = 0x0202,
/// Invalid filter expression.
FilterParseError = 0x0203,
/// Requested K exceeds available vectors.
KTooLarge = 0x0204,
/// Query exceeded time budget.
Timeout = 0x0205,
// ---- Category 0x03: Write Errors ----
/// Another writer holds the lock.
LockHeld = 0x0300,
/// Lock file exists but owner process is dead.
LockStale = 0x0301,
/// Insufficient space for write.
DiskFull = 0x0302,
/// Durable write (fsync) failed.
FsyncFailed = 0x0303,
/// Segment exceeds 4 GB limit.
SegmentTooLarge = 0x0304,
/// File opened in read-only mode.
ReadOnly = 0x0305,
// ---- Category 0x04: Tile Errors (WASM Microkernel) ----
/// WASM trap (OOB, unreachable, stack overflow).
TileTrap = 0x0400,
/// Tile exceeded scratch memory (64 KB).
TileOom = 0x0401,
/// Tile computation exceeded time budget.
TileTimeout = 0x0402,
/// Malformed hub-tile message.
TileInvalidMsg = 0x0403,
/// Operation not available on this profile.
TileUnsupportedOp = 0x0404,
// ---- Category 0x05: Crypto Errors ----
/// Referenced key_id not in CRYPTO_SEG.
KeyNotFound = 0x0500,
/// Key past valid_until timestamp.
KeyExpired = 0x0501,
/// Decryption or auth tag verification failed.
DecryptFailed = 0x0502,
/// Cryptographic algorithm not implemented.
AlgoUnsupported = 0x0503,
/// Attestation quote verification failed.
AttestationInvalid = 0x0504,
/// TEE platform not supported.
PlatformUnsupported = 0x0505,
/// Attestation quote expired or nonce mismatch.
AttestationExpired = 0x0506,
/// Key is not bound to the current TEE measurement.
KeyNotBound = 0x0507,
// ---- Category 0x06: Lineage Errors ----
/// Referenced parent file not found.
ParentNotFound = 0x0600,
/// Parent file hash does not match recorded parent_hash.
ParentHashMismatch = 0x0601,
/// Lineage chain is broken (missing link).
LineageBroken = 0x0602,
/// Lineage chain contains a cycle.
LineageCyclic = 0x0603,
// ---- Category 0x08: Security Errors (ADR-033) ----
/// Level 0 manifest has no signature in Strict/Paranoid mode.
UnsignedManifest = 0x0800,
/// Content hash mismatch on a hotset-referenced segment.
ContentHashMismatch = 0x0801,
/// Manifest signer is not in the trust store.
UnknownSigner = 0x0802,
/// Centroid epoch drift exceeds maximum allowed.
EpochDriftExceeded = 0x0803,
/// Level 1 manifest signature invalid (Paranoid mode).
Level1InvalidSignature = 0x0804,
// ---- Category 0x09: Quality Errors (ADR-033) ----
/// Query result quality is below threshold and AcceptDegraded not set.
QualityBelowThreshold = 0x0900,
/// Per-connection budget tokens exhausted (DoS protection).
BudgetTokensExhausted = 0x0901,
/// Query signature is blacklisted (repeated degenerate queries).
QueryBlacklisted = 0x0902,
// ---- Category 0x07: COW Errors ----
/// COW cluster map is corrupt or unreadable.
CowMapCorrupt = 0x0700,
/// Referenced cluster not found in COW map.
ClusterNotFound = 0x0701,
/// Parent chain is broken (missing ancestor).
ParentChainBroken = 0x0702,
/// Delta patch exceeds compaction threshold.
DeltaThresholdExceeded = 0x0703,
/// Snapshot is frozen and cannot be modified.
SnapshotFrozen = 0x0704,
/// Membership filter is invalid or corrupt.
MembershipInvalid = 0x0705,
/// Generation counter is stale (concurrent modification).
GenerationStale = 0x0706,
/// Kernel binding hash does not match manifest.
KernelBindingMismatch = 0x0707,
/// Double-root manifest is corrupt.
DoubleRootCorrupt = 0x0708,
}
impl ErrorCode {
/// Return the error category (high byte).
#[inline]
pub const fn category(self) -> u8 {
(self as u16 >> 8) as u8
}
/// Return true if this code indicates success (category 0x00).
#[inline]
pub const fn is_success(self) -> bool {
self.category() == 0x00
}
/// Return true if this is a format error (category 0x01), which is generally fatal.
#[inline]
pub const fn is_format_error(self) -> bool {
self.category() == 0x01
}
/// Return true if this is a security error (category 0x08).
#[inline]
pub const fn is_security_error(self) -> bool {
self.category() == 0x08
}
/// Return true if this is a quality error (category 0x09).
#[inline]
pub const fn is_quality_error(self) -> bool {
self.category() == 0x09
}
}
impl TryFrom<u16> for ErrorCode {
type Error = u16;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0x0000 => Ok(Self::Ok),
0x0001 => Ok(Self::OkPartial),
0x0100 => Ok(Self::InvalidMagic),
0x0101 => Ok(Self::InvalidVersion),
0x0102 => Ok(Self::InvalidChecksum),
0x0103 => Ok(Self::InvalidSignature),
0x0104 => Ok(Self::TruncatedSegment),
0x0105 => Ok(Self::InvalidManifest),
0x0106 => Ok(Self::ManifestNotFound),
0x0107 => Ok(Self::UnknownSegmentType),
0x0108 => Ok(Self::AlignmentError),
0x0200 => Ok(Self::DimensionMismatch),
0x0201 => Ok(Self::EmptyIndex),
0x0202 => Ok(Self::MetricUnsupported),
0x0203 => Ok(Self::FilterParseError),
0x0204 => Ok(Self::KTooLarge),
0x0205 => Ok(Self::Timeout),
0x0300 => Ok(Self::LockHeld),
0x0301 => Ok(Self::LockStale),
0x0302 => Ok(Self::DiskFull),
0x0303 => Ok(Self::FsyncFailed),
0x0304 => Ok(Self::SegmentTooLarge),
0x0305 => Ok(Self::ReadOnly),
0x0400 => Ok(Self::TileTrap),
0x0401 => Ok(Self::TileOom),
0x0402 => Ok(Self::TileTimeout),
0x0403 => Ok(Self::TileInvalidMsg),
0x0404 => Ok(Self::TileUnsupportedOp),
0x0500 => Ok(Self::KeyNotFound),
0x0501 => Ok(Self::KeyExpired),
0x0502 => Ok(Self::DecryptFailed),
0x0503 => Ok(Self::AlgoUnsupported),
0x0504 => Ok(Self::AttestationInvalid),
0x0505 => Ok(Self::PlatformUnsupported),
0x0506 => Ok(Self::AttestationExpired),
0x0507 => Ok(Self::KeyNotBound),
0x0600 => Ok(Self::ParentNotFound),
0x0601 => Ok(Self::ParentHashMismatch),
0x0602 => Ok(Self::LineageBroken),
0x0603 => Ok(Self::LineageCyclic),
0x0800 => Ok(Self::UnsignedManifest),
0x0801 => Ok(Self::ContentHashMismatch),
0x0802 => Ok(Self::UnknownSigner),
0x0803 => Ok(Self::EpochDriftExceeded),
0x0804 => Ok(Self::Level1InvalidSignature),
0x0900 => Ok(Self::QualityBelowThreshold),
0x0901 => Ok(Self::BudgetTokensExhausted),
0x0902 => Ok(Self::QueryBlacklisted),
0x0700 => Ok(Self::CowMapCorrupt),
0x0701 => Ok(Self::ClusterNotFound),
0x0702 => Ok(Self::ParentChainBroken),
0x0703 => Ok(Self::DeltaThresholdExceeded),
0x0704 => Ok(Self::SnapshotFrozen),
0x0705 => Ok(Self::MembershipInvalid),
0x0706 => Ok(Self::GenerationStale),
0x0707 => Ok(Self::KernelBindingMismatch),
0x0708 => Ok(Self::DoubleRootCorrupt),
other => Err(other),
}
}
}
/// Rust-idiomatic error type wrapping format-level failures.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RvfError {
/// A wire-level error code was returned.
Code(ErrorCode),
/// The raw u16 did not map to a known error code.
UnknownCode(u16),
/// A segment header had an invalid magic number.
BadMagic { expected: u32, got: u32 },
/// A struct size assertion failed.
SizeMismatch { expected: usize, got: usize },
/// A value was outside the valid enum range.
InvalidEnumValue { type_name: &'static str, value: u64 },
/// Security policy violation during file open (ADR-033 §4).
Security(crate::security::SecurityError),
/// Query result quality is below threshold (ADR-033 §2.4).
/// Contains the QualityEnvelope with partial results and diagnostics.
QualityBelowThreshold {
quality: crate::quality::ResponseQuality,
reason: &'static str,
},
}
impl core::fmt::Display for RvfError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Code(c) => write!(f, "RVF error code 0x{:04X}", *c as u16),
Self::UnknownCode(v) => write!(f, "unknown RVF error code 0x{v:04X}"),
Self::BadMagic { expected, got } => {
write!(f, "bad magic: expected 0x{expected:08X}, got 0x{got:08X}")
}
Self::SizeMismatch { expected, got } => {
write!(f, "size mismatch: expected {expected}, got {got}")
}
Self::InvalidEnumValue { type_name, value } => {
write!(f, "invalid {type_name} value: {value}")
}
Self::Security(e) => write!(f, "security error: {e}"),
Self::QualityBelowThreshold { quality, reason } => {
write!(f, "quality below threshold ({quality:?}): {reason}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn error_code_round_trip_all() {
let codes: &[(u16, ErrorCode)] = &[
(0x0000, ErrorCode::Ok),
(0x0001, ErrorCode::OkPartial),
(0x0100, ErrorCode::InvalidMagic),
(0x0101, ErrorCode::InvalidVersion),
(0x0102, ErrorCode::InvalidChecksum),
(0x0103, ErrorCode::InvalidSignature),
(0x0104, ErrorCode::TruncatedSegment),
(0x0105, ErrorCode::InvalidManifest),
(0x0106, ErrorCode::ManifestNotFound),
(0x0107, ErrorCode::UnknownSegmentType),
(0x0108, ErrorCode::AlignmentError),
(0x0200, ErrorCode::DimensionMismatch),
(0x0201, ErrorCode::EmptyIndex),
(0x0202, ErrorCode::MetricUnsupported),
(0x0203, ErrorCode::FilterParseError),
(0x0204, ErrorCode::KTooLarge),
(0x0205, ErrorCode::Timeout),
(0x0300, ErrorCode::LockHeld),
(0x0301, ErrorCode::LockStale),
(0x0302, ErrorCode::DiskFull),
(0x0303, ErrorCode::FsyncFailed),
(0x0304, ErrorCode::SegmentTooLarge),
(0x0305, ErrorCode::ReadOnly),
(0x0400, ErrorCode::TileTrap),
(0x0401, ErrorCode::TileOom),
(0x0402, ErrorCode::TileTimeout),
(0x0403, ErrorCode::TileInvalidMsg),
(0x0404, ErrorCode::TileUnsupportedOp),
(0x0500, ErrorCode::KeyNotFound),
(0x0501, ErrorCode::KeyExpired),
(0x0502, ErrorCode::DecryptFailed),
(0x0503, ErrorCode::AlgoUnsupported),
(0x0504, ErrorCode::AttestationInvalid),
(0x0505, ErrorCode::PlatformUnsupported),
(0x0506, ErrorCode::AttestationExpired),
(0x0507, ErrorCode::KeyNotBound),
(0x0600, ErrorCode::ParentNotFound),
(0x0601, ErrorCode::ParentHashMismatch),
(0x0602, ErrorCode::LineageBroken),
(0x0603, ErrorCode::LineageCyclic),
(0x0800, ErrorCode::UnsignedManifest),
(0x0801, ErrorCode::ContentHashMismatch),
(0x0802, ErrorCode::UnknownSigner),
(0x0803, ErrorCode::EpochDriftExceeded),
(0x0804, ErrorCode::Level1InvalidSignature),
(0x0900, ErrorCode::QualityBelowThreshold),
(0x0901, ErrorCode::BudgetTokensExhausted),
(0x0902, ErrorCode::QueryBlacklisted),
(0x0700, ErrorCode::CowMapCorrupt),
(0x0701, ErrorCode::ClusterNotFound),
(0x0702, ErrorCode::ParentChainBroken),
(0x0703, ErrorCode::DeltaThresholdExceeded),
(0x0704, ErrorCode::SnapshotFrozen),
(0x0705, ErrorCode::MembershipInvalid),
(0x0706, ErrorCode::GenerationStale),
(0x0707, ErrorCode::KernelBindingMismatch),
(0x0708, ErrorCode::DoubleRootCorrupt),
];
for &(raw, expected) in codes {
assert_eq!(ErrorCode::try_from(raw), Ok(expected), "code 0x{raw:04X}");
assert_eq!(expected as u16, raw);
}
}
#[test]
fn unknown_code() {
assert_eq!(ErrorCode::try_from(0x9999), Err(0x9999));
}
#[test]
fn category_extraction() {
assert_eq!(ErrorCode::Ok.category(), 0x00);
assert_eq!(ErrorCode::InvalidMagic.category(), 0x01);
assert_eq!(ErrorCode::DimensionMismatch.category(), 0x02);
assert_eq!(ErrorCode::LockHeld.category(), 0x03);
assert_eq!(ErrorCode::TileTrap.category(), 0x04);
assert_eq!(ErrorCode::KeyNotFound.category(), 0x05);
assert_eq!(ErrorCode::ParentNotFound.category(), 0x06);
assert_eq!(ErrorCode::CowMapCorrupt.category(), 0x07);
assert_eq!(ErrorCode::UnsignedManifest.category(), 0x08);
assert_eq!(ErrorCode::QualityBelowThreshold.category(), 0x09);
}
#[test]
fn security_error_check() {
assert!(ErrorCode::UnsignedManifest.is_security_error());
assert!(!ErrorCode::Ok.is_security_error());
}
#[test]
fn quality_error_check() {
assert!(ErrorCode::QualityBelowThreshold.is_quality_error());
assert!(!ErrorCode::Ok.is_quality_error());
}
#[test]
fn success_check() {
assert!(ErrorCode::Ok.is_success());
assert!(ErrorCode::OkPartial.is_success());
assert!(!ErrorCode::InvalidMagic.is_success());
}
#[test]
fn format_error_check() {
assert!(ErrorCode::InvalidMagic.is_format_error());
assert!(!ErrorCode::Ok.is_format_error());
assert!(!ErrorCode::DimensionMismatch.is_format_error());
}
#[test]
fn rvf_error_display() {
let e = RvfError::BadMagic {
expected: 0x52564653,
got: 0x00000000,
};
let s = format!("{e}");
assert!(s.contains("bad magic"));
assert!(s.contains("52564653"));
}
#[test]
fn cow_error_check() {
assert_eq!(ErrorCode::CowMapCorrupt as u16, 0x0700);
assert_eq!(ErrorCode::ClusterNotFound as u16, 0x0701);
assert_eq!(ErrorCode::ParentChainBroken as u16, 0x0702);
assert_eq!(ErrorCode::DeltaThresholdExceeded as u16, 0x0703);
assert_eq!(ErrorCode::SnapshotFrozen as u16, 0x0704);
assert_eq!(ErrorCode::MembershipInvalid as u16, 0x0705);
assert_eq!(ErrorCode::GenerationStale as u16, 0x0706);
assert_eq!(ErrorCode::KernelBindingMismatch as u16, 0x0707);
assert_eq!(ErrorCode::DoubleRootCorrupt as u16, 0x0708);
// All COW errors should be category 0x07
assert_eq!(ErrorCode::CowMapCorrupt.category(), 0x07);
assert_eq!(ErrorCode::DoubleRootCorrupt.category(), 0x07);
}
#[test]
fn error_codes_match_spec() {
assert_eq!(ErrorCode::InvalidMagic as u16, 0x0100);
assert_eq!(ErrorCode::InvalidChecksum as u16, 0x0102);
assert_eq!(ErrorCode::ManifestNotFound as u16, 0x0106);
assert_eq!(ErrorCode::AlgoUnsupported as u16, 0x0503);
}
}

View File

@@ -0,0 +1,100 @@
//! Filter operator types for metadata-filtered queries and deletes.
/// Filter operator discriminator.
///
/// Comparison operators use the low nibble (0x00..0x07), logical combinators
/// use the 0x10 range.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum FilterOp {
/// field == value
Eq = 0x00,
/// field != value
Ne = 0x01,
/// field < value
Lt = 0x02,
/// field <= value
Le = 0x03,
/// field > value
Gt = 0x04,
/// field >= value
Ge = 0x05,
/// field in [values]
In = 0x06,
/// field in [low, high)
Range = 0x07,
/// All children must match.
And = 0x10,
/// Any child must match.
Or = 0x11,
/// Negate single child.
Not = 0x12,
}
impl FilterOp {
/// Returns true if this is a logical combinator (AND, OR, NOT).
#[inline]
pub const fn is_logical(self) -> bool {
(self as u8) >= 0x10
}
/// Returns true if this is a comparison operator.
#[inline]
pub const fn is_comparison(self) -> bool {
(self as u8) < 0x10
}
}
impl TryFrom<u8> for FilterOp {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Eq),
0x01 => Ok(Self::Ne),
0x02 => Ok(Self::Lt),
0x03 => Ok(Self::Le),
0x04 => Ok(Self::Gt),
0x05 => Ok(Self::Ge),
0x06 => Ok(Self::In),
0x07 => Ok(Self::Range),
0x10 => Ok(Self::And),
0x11 => Ok(Self::Or),
0x12 => Ok(Self::Not),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_comparison_ops() {
for raw in 0x00..=0x07u8 {
let op = FilterOp::try_from(raw).unwrap();
assert_eq!(op as u8, raw);
assert!(op.is_comparison());
assert!(!op.is_logical());
}
}
#[test]
fn round_trip_logical_ops() {
for raw in [0x10u8, 0x11, 0x12] {
let op = FilterOp::try_from(raw).unwrap();
assert_eq!(op as u8, raw);
assert!(op.is_logical());
assert!(!op.is_comparison());
}
}
#[test]
fn gap_values_are_invalid() {
for raw in 0x08..=0x0Fu8 {
assert_eq!(FilterOp::try_from(raw), Err(raw));
}
}
}

View File

@@ -0,0 +1,137 @@
//! Segment flags bitfield for the RVF format.
/// Bitfield wrapper around the 16-bit segment flags.
///
/// Bits 12-15 are reserved and must be zero.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct SegmentFlags(u16);
impl SegmentFlags {
/// Payload is compressed per the `compression` header field.
pub const COMPRESSED: u16 = 0x0001;
/// Payload is encrypted (key info in CRYPTO_SEG / manifest).
pub const ENCRYPTED: u16 = 0x0002;
/// A signature footer follows the payload.
pub const SIGNED: u16 = 0x0004;
/// Segment is immutable (compaction output).
pub const SEALED: u16 = 0x0008;
/// Segment is a partial / streaming write.
pub const PARTIAL: u16 = 0x0010;
/// Segment logically deletes a prior segment.
pub const TOMBSTONE: u16 = 0x0020;
/// Segment contains temperature-promoted (hot) data.
pub const HOT: u16 = 0x0040;
/// Segment contains overlay / delta data.
pub const OVERLAY: u16 = 0x0080;
/// Segment contains a full snapshot (not delta).
pub const SNAPSHOT: u16 = 0x0100;
/// Segment is a safe rollback point.
pub const CHECKPOINT: u16 = 0x0200;
/// Segment was produced inside an attested TEE environment.
pub const ATTESTED: u16 = 0x0400;
/// File carries DNA-style lineage provenance metadata.
pub const HAS_LINEAGE: u16 = 0x0800;
/// Mask for all defined flag bits.
const KNOWN_MASK: u16 = 0x0FFF;
/// Create an empty flags value (no flags set).
#[inline]
pub const fn empty() -> Self {
Self(0)
}
/// Create flags from a raw `u16`. Reserved bits are masked off.
#[inline]
pub const fn from_raw(raw: u16) -> Self {
Self(raw & Self::KNOWN_MASK)
}
/// Return the raw `u16` representation.
#[inline]
pub const fn bits(self) -> u16 {
self.0
}
/// Check whether a specific flag bit is set.
#[inline]
pub const fn contains(self, flag: u16) -> bool {
self.0 & flag == flag
}
/// Set a flag bit.
#[inline]
pub const fn with(self, flag: u16) -> Self {
Self(self.0 | (flag & Self::KNOWN_MASK))
}
/// Clear a flag bit.
#[inline]
pub const fn without(self, flag: u16) -> Self {
Self(self.0 & !flag)
}
/// Returns true if no flags are set.
#[inline]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_flags() {
let f = SegmentFlags::empty();
assert!(f.is_empty());
assert_eq!(f.bits(), 0);
}
#[test]
fn set_and_check_flags() {
let f = SegmentFlags::empty()
.with(SegmentFlags::COMPRESSED)
.with(SegmentFlags::SEALED);
assert!(f.contains(SegmentFlags::COMPRESSED));
assert!(f.contains(SegmentFlags::SEALED));
assert!(!f.contains(SegmentFlags::ENCRYPTED));
}
#[test]
fn clear_flag() {
let f = SegmentFlags::empty()
.with(SegmentFlags::COMPRESSED)
.with(SegmentFlags::SIGNED)
.without(SegmentFlags::COMPRESSED);
assert!(!f.contains(SegmentFlags::COMPRESSED));
assert!(f.contains(SegmentFlags::SIGNED));
}
#[test]
fn reserved_bits_masked() {
let f = SegmentFlags::from_raw(0xFFFF);
assert_eq!(f.bits(), 0x0FFF);
}
#[test]
fn all_known_flags() {
let all = SegmentFlags::empty()
.with(SegmentFlags::COMPRESSED)
.with(SegmentFlags::ENCRYPTED)
.with(SegmentFlags::SIGNED)
.with(SegmentFlags::SEALED)
.with(SegmentFlags::PARTIAL)
.with(SegmentFlags::TOMBSTONE)
.with(SegmentFlags::HOT)
.with(SegmentFlags::OVERLAY)
.with(SegmentFlags::SNAPSHOT)
.with(SegmentFlags::CHECKPOINT)
.with(SegmentFlags::ATTESTED)
.with(SegmentFlags::HAS_LINEAGE);
assert_eq!(all.bits(), 0x0FFF);
}
}

View File

@@ -0,0 +1,479 @@
//! KERNEL_SEG (0x0E) types for the RVF computational container.
//!
//! Defines the 128-byte `KernelHeader` and associated enums per ADR-030.
//! The KERNEL_SEG embeds a unikernel image that can self-boot an RVF file
//! as a standalone query-serving microservice.
use crate::error::RvfError;
/// Magic number for `KernelHeader`: "RVKN" in big-endian.
pub const KERNEL_MAGIC: u32 = 0x5256_4B4E;
/// Kernel flags: kernel image is cryptographically signed.
pub const KERNEL_FLAG_SIGNED: u32 = 1 << 8;
/// Kernel flags: kernel image is compressed per the `compression` field.
pub const KERNEL_FLAG_COMPRESSED: u32 = 1 << 10;
/// Kernel flags: kernel must run inside a TEE enclave.
pub const KERNEL_FLAG_REQUIRES_TEE: u32 = 1 << 0;
/// Kernel flags: kernel measurement stored in WITNESS_SEG.
pub const KERNEL_FLAG_MEASURED: u32 = 1 << 9;
/// Kernel flags: kernel requires KVM (hardware virtualization).
pub const KERNEL_FLAG_REQUIRES_KVM: u32 = 1 << 1;
/// Kernel flags: kernel requires UEFI boot.
pub const KERNEL_FLAG_REQUIRES_UEFI: u32 = 1 << 2;
/// Kernel flags: kernel includes network stack.
pub const KERNEL_FLAG_HAS_NETWORKING: u32 = 1 << 3;
/// Kernel flags: kernel exposes RVF query API on api_port.
pub const KERNEL_FLAG_HAS_QUERY_API: u32 = 1 << 4;
/// Kernel flags: kernel exposes RVF ingest API.
pub const KERNEL_FLAG_HAS_INGEST_API: u32 = 1 << 5;
/// Kernel flags: kernel exposes health/metrics API.
pub const KERNEL_FLAG_HAS_ADMIN_API: u32 = 1 << 6;
/// Kernel flags: kernel can generate TEE attestation quotes.
pub const KERNEL_FLAG_ATTESTATION_READY: u32 = 1 << 7;
/// Kernel flags: kernel is position-independent.
pub const KERNEL_FLAG_RELOCATABLE: u32 = 1 << 11;
/// Kernel flags: kernel includes VirtIO network driver.
pub const KERNEL_FLAG_HAS_VIRTIO_NET: u32 = 1 << 12;
/// Kernel flags: kernel includes VirtIO block driver.
pub const KERNEL_FLAG_HAS_VIRTIO_BLK: u32 = 1 << 13;
/// Kernel flags: kernel includes VSOCK for host communication.
pub const KERNEL_FLAG_HAS_VSOCK: u32 = 1 << 14;
/// Target CPU architecture for the kernel image.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum KernelArch {
/// AMD64 / Intel 64.
X86_64 = 0x00,
/// ARM 64-bit (ARMv8-A and later).
Aarch64 = 0x01,
/// RISC-V 64-bit (RV64GC).
Riscv64 = 0x02,
/// Architecture-independent (e.g., interpreted).
Universal = 0xFE,
/// Reserved / unspecified.
Unknown = 0xFF,
}
impl TryFrom<u8> for KernelArch {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::X86_64),
0x01 => Ok(Self::Aarch64),
0x02 => Ok(Self::Riscv64),
0xFE => Ok(Self::Universal),
0xFF => Ok(Self::Unknown),
_ => Err(RvfError::InvalidEnumValue {
type_name: "KernelArch",
value: value as u64,
}),
}
}
}
/// Kernel type / runtime model.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum KernelType {
/// Hermit OS unikernel (Rust-native).
Hermit = 0x00,
/// Minimal Linux kernel (bzImage compatible).
MicroLinux = 0x01,
/// Asterinas framekernel (Linux ABI compatible).
Asterinas = 0x02,
/// WASI Preview 2 component (alternative to WASM_SEG).
WasiPreview2 = 0x03,
/// Custom kernel (requires external VMM knowledge).
Custom = 0x04,
/// Test stub for CI (boots, reports health, exits).
TestStub = 0xFE,
}
impl TryFrom<u8> for KernelType {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Hermit),
0x01 => Ok(Self::MicroLinux),
0x02 => Ok(Self::Asterinas),
0x03 => Ok(Self::WasiPreview2),
0x04 => Ok(Self::Custom),
0xFE => Ok(Self::TestStub),
_ => Err(RvfError::InvalidEnumValue {
type_name: "KernelType",
value: value as u64,
}),
}
}
}
/// Transport mechanism for the kernel's query API.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum ApiTransport {
/// HTTP/1.1 over TCP (default).
TcpHttp = 0x00,
/// gRPC over TCP (HTTP/2).
TcpGrpc = 0x01,
/// VirtIO socket (Firecracker host<->guest).
Vsock = 0x02,
/// Shared memory region (for same-host co-location).
SharedMem = 0x03,
/// No network API (batch mode only).
None = 0xFF,
}
impl TryFrom<u8> for ApiTransport {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::TcpHttp),
0x01 => Ok(Self::TcpGrpc),
0x02 => Ok(Self::Vsock),
0x03 => Ok(Self::SharedMem),
0xFF => Ok(Self::None),
_ => Err(RvfError::InvalidEnumValue {
type_name: "ApiTransport",
value: value as u64,
}),
}
}
}
/// 128-byte header for KERNEL_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire except `api_port` which is network byte order
/// (big-endian) per ADR-030.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct KernelHeader {
/// Magic: `KERNEL_MAGIC` (0x52564B4E, "RVKN").
pub kernel_magic: u32,
/// KernelHeader format version (currently 1).
pub header_version: u16,
/// Target architecture (see `KernelArch`).
pub arch: u8,
/// Kernel type (see `KernelType`).
pub kernel_type: u8,
/// Bitfield flags (see `KERNEL_FLAG_*` constants).
pub kernel_flags: u32,
/// Minimum RAM required (MiB).
pub min_memory_mb: u32,
/// Virtual address of kernel entry point.
pub entry_point: u64,
/// Uncompressed kernel image size (bytes).
pub image_size: u64,
/// Compressed kernel image size (bytes).
pub compressed_size: u64,
/// Compression algorithm (same enum as `SegmentHeader.compression`).
pub compression: u8,
/// API transport (see `ApiTransport`).
pub api_transport: u8,
/// Default API port (network byte order).
pub api_port: u16,
/// Supported RVF query API version.
pub api_version: u32,
/// SHAKE-256-256 of uncompressed kernel image.
pub image_hash: [u8; 32],
/// Unique build identifier (UUID v7).
pub build_id: [u8; 16],
/// Build time (nanosecond UNIX timestamp).
pub build_timestamp: u64,
/// Recommended vCPU count (0 = single).
pub vcpu_count: u32,
/// Reserved (must be zero).
pub reserved_0: u32,
/// Offset to kernel command line within payload.
pub cmdline_offset: u64,
/// Length of kernel command line (bytes).
pub cmdline_length: u32,
/// Reserved (must be zero).
pub reserved_1: u32,
}
// Compile-time assertion: KernelHeader must be exactly 128 bytes.
const _: () = assert!(core::mem::size_of::<KernelHeader>() == 128);
impl KernelHeader {
/// Serialize the header to a 128-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 128] {
let mut buf = [0u8; 128];
buf[0x00..0x04].copy_from_slice(&self.kernel_magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.header_version.to_le_bytes());
buf[0x06] = self.arch;
buf[0x07] = self.kernel_type;
buf[0x08..0x0C].copy_from_slice(&self.kernel_flags.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&self.min_memory_mb.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&self.entry_point.to_le_bytes());
buf[0x18..0x20].copy_from_slice(&self.image_size.to_le_bytes());
buf[0x20..0x28].copy_from_slice(&self.compressed_size.to_le_bytes());
buf[0x28] = self.compression;
buf[0x29] = self.api_transport;
buf[0x2A..0x2C].copy_from_slice(&self.api_port.to_be_bytes());
buf[0x2C..0x30].copy_from_slice(&self.api_version.to_le_bytes());
buf[0x30..0x50].copy_from_slice(&self.image_hash);
buf[0x50..0x60].copy_from_slice(&self.build_id);
buf[0x60..0x68].copy_from_slice(&self.build_timestamp.to_le_bytes());
buf[0x68..0x6C].copy_from_slice(&self.vcpu_count.to_le_bytes());
buf[0x6C..0x70].copy_from_slice(&self.reserved_0.to_le_bytes());
buf[0x70..0x78].copy_from_slice(&self.cmdline_offset.to_le_bytes());
buf[0x78..0x7C].copy_from_slice(&self.cmdline_length.to_le_bytes());
buf[0x7C..0x80].copy_from_slice(&self.reserved_1.to_le_bytes());
buf
}
/// Deserialize a `KernelHeader` from a 128-byte slice.
pub fn from_bytes(data: &[u8; 128]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != KERNEL_MAGIC {
return Err(RvfError::BadMagic {
expected: KERNEL_MAGIC,
got: magic,
});
}
Ok(Self {
kernel_magic: magic,
header_version: u16::from_le_bytes([data[0x04], data[0x05]]),
arch: data[0x06],
kernel_type: data[0x07],
kernel_flags: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
min_memory_mb: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
entry_point: u64::from_le_bytes([
data[0x10], data[0x11], data[0x12], data[0x13], data[0x14], data[0x15], data[0x16],
data[0x17],
]),
image_size: u64::from_le_bytes([
data[0x18], data[0x19], data[0x1A], data[0x1B], data[0x1C], data[0x1D], data[0x1E],
data[0x1F],
]),
compressed_size: u64::from_le_bytes([
data[0x20], data[0x21], data[0x22], data[0x23], data[0x24], data[0x25], data[0x26],
data[0x27],
]),
compression: data[0x28],
api_transport: data[0x29],
api_port: u16::from_be_bytes([data[0x2A], data[0x2B]]),
api_version: u32::from_le_bytes([data[0x2C], data[0x2D], data[0x2E], data[0x2F]]),
image_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x30..0x50]);
h
},
build_id: {
let mut id = [0u8; 16];
id.copy_from_slice(&data[0x50..0x60]);
id
},
build_timestamp: u64::from_le_bytes([
data[0x60], data[0x61], data[0x62], data[0x63], data[0x64], data[0x65], data[0x66],
data[0x67],
]),
vcpu_count: u32::from_le_bytes([data[0x68], data[0x69], data[0x6A], data[0x6B]]),
reserved_0: u32::from_le_bytes([data[0x6C], data[0x6D], data[0x6E], data[0x6F]]),
cmdline_offset: u64::from_le_bytes([
data[0x70], data[0x71], data[0x72], data[0x73], data[0x74], data[0x75], data[0x76],
data[0x77],
]),
cmdline_length: u32::from_le_bytes([data[0x78], data[0x79], data[0x7A], data[0x7B]]),
reserved_1: u32::from_le_bytes([data[0x7C], data[0x7D], data[0x7E], data[0x7F]]),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> KernelHeader {
KernelHeader {
kernel_magic: KERNEL_MAGIC,
header_version: 1,
arch: KernelArch::X86_64 as u8,
kernel_type: KernelType::Hermit as u8,
kernel_flags: KERNEL_FLAG_HAS_QUERY_API | KERNEL_FLAG_COMPRESSED,
min_memory_mb: 32,
entry_point: 0x0020_0000,
image_size: 400_000,
compressed_size: 180_000,
compression: 2, // ZSTD
api_transport: ApiTransport::TcpHttp as u8,
api_port: 8080,
api_version: 1,
image_hash: [0xAB; 32],
build_id: [0xCD; 16],
build_timestamp: 1_700_000_000_000_000_000,
vcpu_count: 1,
reserved_0: 0,
cmdline_offset: 128,
cmdline_length: 64,
reserved_1: 0,
}
}
#[test]
fn header_size_is_128() {
assert_eq!(core::mem::size_of::<KernelHeader>(), 128);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = KERNEL_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVKN");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = KernelHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.kernel_magic, KERNEL_MAGIC);
assert_eq!(decoded.header_version, 1);
assert_eq!(decoded.arch, KernelArch::X86_64 as u8);
assert_eq!(decoded.kernel_type, KernelType::Hermit as u8);
assert_eq!(
decoded.kernel_flags,
KERNEL_FLAG_HAS_QUERY_API | KERNEL_FLAG_COMPRESSED
);
assert_eq!(decoded.min_memory_mb, 32);
assert_eq!(decoded.entry_point, 0x0020_0000);
assert_eq!(decoded.image_size, 400_000);
assert_eq!(decoded.compressed_size, 180_000);
assert_eq!(decoded.compression, 2);
assert_eq!(decoded.api_transport, ApiTransport::TcpHttp as u8);
assert_eq!(decoded.api_port, 8080);
assert_eq!(decoded.api_version, 1);
assert_eq!(decoded.image_hash, [0xAB; 32]);
assert_eq!(decoded.build_id, [0xCD; 16]);
assert_eq!(decoded.build_timestamp, 1_700_000_000_000_000_000);
assert_eq!(decoded.vcpu_count, 1);
assert_eq!(decoded.reserved_0, 0);
assert_eq!(decoded.cmdline_offset, 128);
assert_eq!(decoded.cmdline_length, 64);
assert_eq!(decoded.reserved_1, 0);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = KernelHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, KERNEL_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.kernel_magic as *const _ as usize - base, 0x00);
assert_eq!(&h.header_version as *const _ as usize - base, 0x04);
assert_eq!(&h.arch as *const _ as usize - base, 0x06);
assert_eq!(&h.kernel_type as *const _ as usize - base, 0x07);
assert_eq!(&h.kernel_flags as *const _ as usize - base, 0x08);
assert_eq!(&h.min_memory_mb as *const _ as usize - base, 0x0C);
assert_eq!(&h.entry_point as *const _ as usize - base, 0x10);
assert_eq!(&h.image_size as *const _ as usize - base, 0x18);
assert_eq!(&h.compressed_size as *const _ as usize - base, 0x20);
assert_eq!(&h.compression as *const _ as usize - base, 0x28);
assert_eq!(&h.api_transport as *const _ as usize - base, 0x29);
assert_eq!(&h.api_port as *const _ as usize - base, 0x2A);
assert_eq!(&h.api_version as *const _ as usize - base, 0x2C);
assert_eq!(&h.image_hash as *const _ as usize - base, 0x30);
assert_eq!(&h.build_id as *const _ as usize - base, 0x50);
assert_eq!(&h.build_timestamp as *const _ as usize - base, 0x60);
assert_eq!(&h.vcpu_count as *const _ as usize - base, 0x68);
assert_eq!(&h.reserved_0 as *const _ as usize - base, 0x6C);
assert_eq!(&h.cmdline_offset as *const _ as usize - base, 0x70);
assert_eq!(&h.cmdline_length as *const _ as usize - base, 0x78);
assert_eq!(&h.reserved_1 as *const _ as usize - base, 0x7C);
}
#[test]
fn kernel_arch_try_from() {
assert_eq!(KernelArch::try_from(0x00), Ok(KernelArch::X86_64));
assert_eq!(KernelArch::try_from(0x01), Ok(KernelArch::Aarch64));
assert_eq!(KernelArch::try_from(0x02), Ok(KernelArch::Riscv64));
assert_eq!(KernelArch::try_from(0xFE), Ok(KernelArch::Universal));
assert_eq!(KernelArch::try_from(0xFF), Ok(KernelArch::Unknown));
assert!(KernelArch::try_from(0x03).is_err());
assert!(KernelArch::try_from(0x80).is_err());
}
#[test]
fn kernel_type_try_from() {
assert_eq!(KernelType::try_from(0x00), Ok(KernelType::Hermit));
assert_eq!(KernelType::try_from(0x01), Ok(KernelType::MicroLinux));
assert_eq!(KernelType::try_from(0x02), Ok(KernelType::Asterinas));
assert_eq!(KernelType::try_from(0x03), Ok(KernelType::WasiPreview2));
assert_eq!(KernelType::try_from(0x04), Ok(KernelType::Custom));
assert_eq!(KernelType::try_from(0xFE), Ok(KernelType::TestStub));
assert!(KernelType::try_from(0x05).is_err());
assert!(KernelType::try_from(0xFF).is_err());
}
#[test]
fn api_transport_try_from() {
assert_eq!(ApiTransport::try_from(0x00), Ok(ApiTransport::TcpHttp));
assert_eq!(ApiTransport::try_from(0x01), Ok(ApiTransport::TcpGrpc));
assert_eq!(ApiTransport::try_from(0x02), Ok(ApiTransport::Vsock));
assert_eq!(ApiTransport::try_from(0x03), Ok(ApiTransport::SharedMem));
assert_eq!(ApiTransport::try_from(0xFF), Ok(ApiTransport::None));
assert!(ApiTransport::try_from(0x04).is_err());
assert!(ApiTransport::try_from(0x80).is_err());
}
#[test]
fn kernel_flags_bit_positions() {
assert_eq!(KERNEL_FLAG_REQUIRES_TEE, 0x0001);
assert_eq!(KERNEL_FLAG_REQUIRES_KVM, 0x0002);
assert_eq!(KERNEL_FLAG_REQUIRES_UEFI, 0x0004);
assert_eq!(KERNEL_FLAG_HAS_NETWORKING, 0x0008);
assert_eq!(KERNEL_FLAG_HAS_QUERY_API, 0x0010);
assert_eq!(KERNEL_FLAG_HAS_INGEST_API, 0x0020);
assert_eq!(KERNEL_FLAG_HAS_ADMIN_API, 0x0040);
assert_eq!(KERNEL_FLAG_ATTESTATION_READY, 0x0080);
assert_eq!(KERNEL_FLAG_SIGNED, 0x0100);
assert_eq!(KERNEL_FLAG_MEASURED, 0x0200);
assert_eq!(KERNEL_FLAG_COMPRESSED, 0x0400);
assert_eq!(KERNEL_FLAG_RELOCATABLE, 0x0800);
assert_eq!(KERNEL_FLAG_HAS_VIRTIO_NET, 0x1000);
assert_eq!(KERNEL_FLAG_HAS_VIRTIO_BLK, 0x2000);
assert_eq!(KERNEL_FLAG_HAS_VSOCK, 0x4000);
}
#[test]
fn api_port_network_byte_order() {
let mut h = sample_header();
h.api_port = 0x1F90; // 8080
let bytes = h.to_bytes();
// api_port at offset 0x2A, big-endian
assert_eq!(bytes[0x2A], 0x1F);
assert_eq!(bytes[0x2B], 0x90);
let decoded = KernelHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.api_port, 0x1F90);
}
#[test]
fn zero_filled_reserved_fields() {
let h = sample_header();
let bytes = h.to_bytes();
// reserved_0 at 0x6C..0x70 should be zero
assert_eq!(&bytes[0x6C..0x70], &[0, 0, 0, 0]);
// reserved_1 at 0x7C..0x80 should be zero
assert_eq!(&bytes[0x7C..0x80], &[0, 0, 0, 0]);
}
}

View File

@@ -0,0 +1,186 @@
//! Kernel binding types for the RVF computational container.
//!
//! Defines the 128-byte `KernelBinding` struct per ADR-031 (revised).
//! A `KernelBinding` cryptographically ties a manifest root to a
//! policy hash with a version stamp, ensuring tamper-evident linkage.
//!
//! Padded to 128 bytes to avoid future wire-format breaks. Active fields
//! occupy 76 bytes; the remaining 52 bytes are reserved/padding (must be zero).
/// 128-byte kernel binding record (padded for future evolution).
///
/// Layout:
/// | Offset | Size | Field |
/// |--------|------|----------------------|
/// | 0x00 | 32 | manifest_root_hash |
/// | 0x20 | 32 | policy_hash |
/// | 0x40 | 2 | binding_version |
/// | 0x42 | 2 | min_runtime_version |
/// | 0x44 | 4 | _pad0 (alignment) |
/// | 0x48 | 8 | allowed_segment_mask |
/// | 0x50 | 48 | _reserved |
///
/// All multi-byte fields are little-endian on the wire.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct KernelBinding {
/// SHAKE-256-256 of the manifest root node.
pub manifest_root_hash: [u8; 32],
/// SHAKE-256-256 of the policy document.
pub policy_hash: [u8; 32],
/// Binding format version (currently 1).
pub binding_version: u16,
/// Minimum runtime version required (0 = any).
pub min_runtime_version: u16,
/// Alignment padding (must be zero).
pub _pad0: u32,
/// Bitmask of allowed segment types (0 = no restriction).
pub allowed_segment_mask: u64,
/// Reserved for future use (must be zero).
pub _reserved: [u8; 48],
}
// Compile-time assertion: KernelBinding must be exactly 128 bytes.
const _: () = assert!(core::mem::size_of::<KernelBinding>() == 128);
impl KernelBinding {
/// Serialize the binding to a 128-byte array.
pub fn to_bytes(&self) -> [u8; 128] {
let mut buf = [0u8; 128];
buf[0x00..0x20].copy_from_slice(&self.manifest_root_hash);
buf[0x20..0x40].copy_from_slice(&self.policy_hash);
buf[0x40..0x42].copy_from_slice(&self.binding_version.to_le_bytes());
buf[0x42..0x44].copy_from_slice(&self.min_runtime_version.to_le_bytes());
buf[0x44..0x48].copy_from_slice(&self._pad0.to_le_bytes());
buf[0x48..0x50].copy_from_slice(&self.allowed_segment_mask.to_le_bytes());
buf[0x50..0x80].copy_from_slice(&self._reserved);
buf
}
/// Deserialize a `KernelBinding` from a 128-byte slice (unchecked).
///
/// Does NOT validate reserved fields. Use `from_bytes_validated` for
/// security-critical paths that must reject non-zero padding/reserved.
pub fn from_bytes(data: &[u8; 128]) -> Self {
Self {
manifest_root_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x00..0x20]);
h
},
policy_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x20..0x40]);
h
},
binding_version: u16::from_le_bytes([data[0x40], data[0x41]]),
min_runtime_version: u16::from_le_bytes([data[0x42], data[0x43]]),
_pad0: u32::from_le_bytes([data[0x44], data[0x45], data[0x46], data[0x47]]),
allowed_segment_mask: u64::from_le_bytes([
data[0x48], data[0x49], data[0x4A], data[0x4B], data[0x4C], data[0x4D], data[0x4E],
data[0x4F],
]),
_reserved: {
let mut r = [0u8; 48];
r.copy_from_slice(&data[0x50..0x80]);
r
},
}
}
/// Deserialize and validate a `KernelBinding` from a 128-byte slice.
///
/// Rejects bindings where:
/// - `binding_version` is 0 (uninitialized)
/// - `_pad0` is non-zero (spec violation)
/// - `_reserved` contains non-zero bytes (spec violation / data smuggling)
pub fn from_bytes_validated(data: &[u8; 128]) -> Result<Self, &'static str> {
let binding = Self::from_bytes(data);
if binding.binding_version == 0 {
return Err("binding_version must be > 0");
}
if binding._pad0 != 0 {
return Err("_pad0 must be zero");
}
if binding._reserved.iter().any(|&b| b != 0) {
return Err("_reserved must be all zeros");
}
Ok(binding)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_binding() -> KernelBinding {
KernelBinding {
manifest_root_hash: [0xAA; 32],
policy_hash: [0xBB; 32],
binding_version: 1,
min_runtime_version: 0,
_pad0: 0,
allowed_segment_mask: 0,
_reserved: [0; 48],
}
}
#[test]
fn binding_size_is_128() {
assert_eq!(core::mem::size_of::<KernelBinding>(), 128);
}
#[test]
fn round_trip_serialization() {
let original = sample_binding();
let bytes = original.to_bytes();
let decoded = KernelBinding::from_bytes(&bytes);
assert_eq!(decoded.manifest_root_hash, [0xAA; 32]);
assert_eq!(decoded.policy_hash, [0xBB; 32]);
assert_eq!(decoded.binding_version, 1);
assert_eq!(decoded.min_runtime_version, 0);
assert_eq!(decoded._pad0, 0);
assert_eq!(decoded.allowed_segment_mask, 0);
assert_eq!(decoded._reserved, [0; 48]);
}
#[test]
fn round_trip_with_fields() {
let binding = KernelBinding {
manifest_root_hash: [0x11; 32],
policy_hash: [0x22; 32],
binding_version: 2,
min_runtime_version: 3,
_pad0: 0,
allowed_segment_mask: 0x00FF_FFFF,
_reserved: [0; 48],
};
let bytes = binding.to_bytes();
let decoded = KernelBinding::from_bytes(&bytes);
assert_eq!(decoded.binding_version, 2);
assert_eq!(decoded.min_runtime_version, 3);
assert_eq!(decoded.allowed_segment_mask, 0x00FF_FFFF);
}
#[test]
fn field_offsets() {
let b = sample_binding();
let base = &b as *const _ as usize;
assert_eq!(&b.manifest_root_hash as *const _ as usize - base, 0x00);
assert_eq!(&b.policy_hash as *const _ as usize - base, 0x20);
assert_eq!(&b.binding_version as *const _ as usize - base, 0x40);
assert_eq!(&b.min_runtime_version as *const _ as usize - base, 0x42);
assert_eq!(&b._pad0 as *const _ as usize - base, 0x44);
assert_eq!(&b.allowed_segment_mask as *const _ as usize - base, 0x48);
assert_eq!(&b._reserved as *const _ as usize - base, 0x50);
}
#[test]
fn reserved_must_be_zero_in_new_bindings() {
let b = sample_binding();
assert!(b._reserved.iter().all(|&x| x == 0));
assert_eq!(b._pad0, 0);
}
}

View File

@@ -0,0 +1,125 @@
//! Core types for the RuVector Format (RVF).
//!
//! This crate provides the foundational types shared across all RVF crates:
//! segment headers, type enums, flags, error codes, and format constants.
//!
//! All types are `no_std` compatible by default.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
// Tests always need alloc (for Vec, format!, etc.) even without the feature.
#[cfg(all(test, not(feature = "alloc")))]
extern crate alloc;
pub mod agi_container;
pub mod attestation;
pub mod checksum;
pub mod compression;
pub mod constants;
pub mod cow_map;
pub mod dashboard;
pub mod data_type;
pub mod delta;
pub mod ebpf;
#[cfg(feature = "ed25519")]
pub mod ed25519;
pub mod error;
pub mod filter;
pub mod flags;
pub mod kernel;
pub mod kernel_binding;
pub mod lineage;
pub mod manifest;
pub mod membership;
pub mod profile;
pub mod qr_seed;
pub mod quality;
pub mod quant_type;
pub mod refcount;
pub mod security;
pub mod segment;
pub mod segment_type;
pub mod sha256;
pub mod signature;
pub mod wasm_bootstrap;
pub mod witness;
pub use agi_container::{
AgiContainerHeader, AuthorityLevel, CoherenceThresholds, ContainerError, ContainerSegments,
ExecutionMode, ResourceBudget, AGI_HAS_COHERENCE_GATES, AGI_HAS_DOMAIN_EXPANSION, AGI_HAS_EVAL,
AGI_HAS_KERNEL, AGI_HAS_ORCHESTRATOR, AGI_HAS_SKILLS, AGI_HAS_TOOLS, AGI_HAS_WASM,
AGI_HAS_WITNESS, AGI_HAS_WORLD_MODEL, AGI_HEADER_SIZE, AGI_MAGIC, AGI_MAX_CONTAINER_SIZE,
AGI_OFFLINE_CAPABLE, AGI_REPLAY_CAPABLE, AGI_SIGNED, AGI_TAG_AUTHORITY_CONFIG,
AGI_TAG_COST_CURVE, AGI_TAG_COUNTEREXAMPLES, AGI_TAG_DOMAIN_PROFILE, AGI_TAG_POLICY_KERNEL,
AGI_TAG_TRANSFER_PRIOR,
};
pub use attestation::{AttestationHeader, AttestationWitnessType, TeePlatform, KEY_TYPE_TEE_BOUND};
pub use checksum::ChecksumAlgo;
pub use compression::CompressionAlgo;
pub use constants::*;
pub use cow_map::{CowMapEntry, CowMapHeader, MapFormat, COWMAP_MAGIC};
pub use dashboard::{DashboardHeader, DASHBOARD_MAGIC, DASHBOARD_MAX_SIZE};
pub use data_type::DataType;
pub use delta::{DeltaEncoding, DeltaHeader, DELTA_MAGIC};
pub use ebpf::{EbpfAttachType, EbpfHeader, EbpfProgramType, EBPF_MAGIC};
#[cfg(feature = "ed25519")]
pub use ed25519::{
ct_eq_sig, ed25519_sign, ed25519_verify, Ed25519Keypair,
PUBLIC_KEY_SIZE as ED25519_PUBLIC_KEY_SIZE, SECRET_KEY_SIZE as ED25519_SECRET_KEY_SIZE,
SIGNATURE_SIZE as ED25519_SIGNATURE_SIZE,
};
pub use error::{ErrorCode, RvfError};
pub use filter::FilterOp;
pub use flags::SegmentFlags;
pub use kernel::{
ApiTransport, KernelArch, KernelHeader, KernelType, KERNEL_FLAG_ATTESTATION_READY,
KERNEL_FLAG_COMPRESSED, KERNEL_FLAG_HAS_ADMIN_API, KERNEL_FLAG_HAS_INGEST_API,
KERNEL_FLAG_HAS_NETWORKING, KERNEL_FLAG_HAS_QUERY_API, KERNEL_FLAG_HAS_VIRTIO_BLK,
KERNEL_FLAG_HAS_VIRTIO_NET, KERNEL_FLAG_HAS_VSOCK, KERNEL_FLAG_MEASURED,
KERNEL_FLAG_RELOCATABLE, KERNEL_FLAG_REQUIRES_KVM, KERNEL_FLAG_REQUIRES_TEE,
KERNEL_FLAG_REQUIRES_UEFI, KERNEL_FLAG_SIGNED, KERNEL_MAGIC,
};
pub use kernel_binding::KernelBinding;
pub use lineage::{
DerivationType, FileIdentity, LineageRecord, LINEAGE_RECORD_SIZE, WITNESS_DERIVATION,
WITNESS_LINEAGE_MERGE, WITNESS_LINEAGE_SNAPSHOT, WITNESS_LINEAGE_TRANSFORM,
WITNESS_LINEAGE_VERIFY,
};
pub use manifest::{
CentroidPtr, EntrypointPtr, HotCachePtr, Level0Root, PrefetchMapPtr, QuantDictPtr, TopLayerPtr,
};
pub use membership::{FilterMode, FilterType, MembershipHeader, MEMBERSHIP_MAGIC};
pub use profile::{DomainProfile, ProfileId};
pub use qr_seed::{
HostEntry, LayerEntry, SeedHeader, QR_MAX_BYTES, SEED_COMPRESSED, SEED_ENCRYPTED,
SEED_HAS_DOWNLOAD, SEED_HAS_MICROKERNEL, SEED_HAS_VECTORS, SEED_HEADER_SIZE, SEED_MAGIC,
SEED_OFFLINE_CAPABLE, SEED_SIGNED, SEED_STREAM_UPGRADE,
};
pub use quality::{
derive_response_quality, BudgetReport, BudgetType, DegradationReason, DegradationReport,
FallbackPath, IndexLayersUsed, QualityPreference, ResponseQuality, RetrievalQuality,
SafetyNetBudget, SearchEvidenceSummary,
};
pub use quant_type::QuantType;
pub use refcount::{RefcountHeader, REFCOUNT_MAGIC};
pub use security::{HardeningFields, SecurityError, SecurityPolicy};
pub use segment::SegmentHeader;
pub use segment_type::SegmentType;
pub use sha256::{hmac_sha256, sha256, Sha256};
pub use signature::{SignatureAlgo, SignatureFooter};
pub use wasm_bootstrap::{
WasmHeader, WasmRole, WasmTarget, WASM_FEAT_BULK_MEMORY, WASM_FEAT_EXCEPTION_HANDLING,
WASM_FEAT_GC, WASM_FEAT_MULTI_VALUE, WASM_FEAT_REFERENCE_TYPES, WASM_FEAT_SIMD,
WASM_FEAT_TAIL_CALL, WASM_FEAT_THREADS, WASM_MAGIC,
};
pub use witness::{
GovernanceMode, PolicyCheck, Scorecard, TaskOutcome, WitnessHeader, WITNESS_HEADER_SIZE,
WITNESS_MAGIC, WIT_HAS_DIFF, WIT_HAS_PLAN, WIT_HAS_POSTMORTEM, WIT_HAS_SPEC, WIT_HAS_TEST_LOG,
WIT_HAS_TRACE, WIT_SIGNED, WIT_TAG_DIFF, WIT_TAG_PLAN, WIT_TAG_POSTMORTEM, WIT_TAG_SPEC,
WIT_TAG_TEST_LOG, WIT_TAG_TRACE,
};
#[cfg(feature = "alloc")]
pub use witness::{ToolCallEntry, TOOL_CALL_FIXED_SIZE};

View File

@@ -0,0 +1,337 @@
//! DNA-style lineage provenance types for RVF files.
//!
//! Each RVF file carries a `FileIdentity` in the Level0Root reserved area,
//! enabling provenance chains: parent→child→grandchild with hash verification.
/// Derivation type describing how a child file was produced from its parent.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum DerivationType {
/// Exact copy of the parent.
Clone = 0,
/// Subset of parent data (filtered).
Filter = 1,
/// Multiple parents merged into one.
Merge = 2,
/// Re-quantized from parent.
Quantize = 3,
/// Re-indexed (HNSW rebuild, etc.).
Reindex = 4,
/// Arbitrary transformation.
Transform = 5,
/// Point-in-time snapshot.
Snapshot = 6,
/// User-defined derivation.
UserDefined = 0xFF,
}
impl TryFrom<u8> for DerivationType {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Clone),
1 => Ok(Self::Filter),
2 => Ok(Self::Merge),
3 => Ok(Self::Quantize),
4 => Ok(Self::Reindex),
5 => Ok(Self::Transform),
6 => Ok(Self::Snapshot),
0xFF => Ok(Self::UserDefined),
other => Err(other),
}
}
}
/// File identity embedded in the Level0Root reserved area at offset 0xF00.
///
/// Exactly 68 bytes, fitting within the 252-byte reserved area.
/// Old readers that ignore the reserved area see zeros and continue working.
///
/// Layout:
/// | Offset | Size | Field |
/// |--------|------|----------------|
/// | 0x00 | 16 | file_id |
/// | 0x10 | 16 | parent_id |
/// | 0x20 | 32 | parent_hash |
/// | 0x40 | 4 | lineage_depth |
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct FileIdentity {
/// Unique identifier for this file (UUID-style, 16 bytes).
pub file_id: [u8; 16],
/// Identifier of the parent file (all zeros for root files).
pub parent_id: [u8; 16],
/// SHAKE-256-256 hash of the parent's manifest (all zeros for root).
pub parent_hash: [u8; 32],
/// Lineage depth: 0 for root, incremented for each derivation.
pub lineage_depth: u32,
}
// Compile-time assertion: FileIdentity must be exactly 68 bytes.
const _: () = assert!(core::mem::size_of::<FileIdentity>() == 68);
impl FileIdentity {
/// Create a root identity (no parent) with the given file_id.
pub const fn new_root(file_id: [u8; 16]) -> Self {
Self {
file_id,
parent_id: [0u8; 16],
parent_hash: [0u8; 32],
lineage_depth: 0,
}
}
/// Returns true if this is a root identity (no parent).
pub fn is_root(&self) -> bool {
self.parent_id == [0u8; 16] && self.lineage_depth == 0
}
/// Create an all-zero identity (default for files without lineage).
pub const fn zeroed() -> Self {
Self {
file_id: [0u8; 16],
parent_id: [0u8; 16],
parent_hash: [0u8; 32],
lineage_depth: 0,
}
}
/// Serialize to a 68-byte array.
pub fn to_bytes(&self) -> [u8; 68] {
let mut buf = [0u8; 68];
buf[0..16].copy_from_slice(&self.file_id);
buf[16..32].copy_from_slice(&self.parent_id);
buf[32..64].copy_from_slice(&self.parent_hash);
buf[64..68].copy_from_slice(&self.lineage_depth.to_le_bytes());
buf
}
/// Deserialize from a 68-byte slice.
pub fn from_bytes(data: &[u8; 68]) -> Self {
let mut file_id = [0u8; 16];
file_id.copy_from_slice(&data[0..16]);
let mut parent_id = [0u8; 16];
parent_id.copy_from_slice(&data[16..32]);
let mut parent_hash = [0u8; 32];
parent_hash.copy_from_slice(&data[32..64]);
// Safety: data is &[u8; 68], so data[64..68] is always exactly 4 bytes.
// Use an explicit array conversion to avoid the unwrap.
let lineage_depth = u32::from_le_bytes([data[64], data[65], data[66], data[67]]);
Self {
file_id,
parent_id,
parent_hash,
lineage_depth,
}
}
}
/// A lineage record for witness chain entries.
///
/// Fixed 128 bytes with a 47-byte description field.
///
/// Layout:
/// | Offset | Size | Field |
/// |--------|------|------------------|
/// | 0x00 | 16 | file_id |
/// | 0x10 | 16 | parent_id |
/// | 0x20 | 32 | parent_hash |
/// | 0x40 | 1 | derivation_type |
/// | 0x41 | 3 | _pad |
/// | 0x44 | 4 | mutation_count |
/// | 0x48 | 8 | timestamp_ns |
/// | 0x50 | 1 | description_len |
/// | 0x51 | 47 | description |
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LineageRecord {
/// Unique identifier for this file.
pub file_id: [u8; 16],
/// Identifier of the parent file.
pub parent_id: [u8; 16],
/// SHAKE-256-256 hash of the parent's manifest.
pub parent_hash: [u8; 32],
/// How the child was derived from the parent.
pub derivation_type: DerivationType,
/// Number of mutations/changes applied.
pub mutation_count: u32,
/// Nanosecond UNIX timestamp of derivation.
pub timestamp_ns: u64,
/// Length of the description (max 47).
pub description_len: u8,
/// UTF-8 description of the derivation (47-byte buffer).
pub description: [u8; 47],
}
/// Size of a serialized LineageRecord.
pub const LINEAGE_RECORD_SIZE: usize = 128;
impl LineageRecord {
/// Create a new lineage record with a description string.
pub fn new(
file_id: [u8; 16],
parent_id: [u8; 16],
parent_hash: [u8; 32],
derivation_type: DerivationType,
mutation_count: u32,
timestamp_ns: u64,
desc: &str,
) -> Self {
let desc_bytes = desc.as_bytes();
let desc_len = desc_bytes.len().min(47) as u8;
let mut description = [0u8; 47];
description[..desc_len as usize].copy_from_slice(&desc_bytes[..desc_len as usize]);
Self {
file_id,
parent_id,
parent_hash,
derivation_type,
mutation_count,
timestamp_ns,
description_len: desc_len,
description,
}
}
/// Get the description as a string slice.
pub fn description_str(&self) -> &str {
let len = (self.description_len as usize).min(47);
core::str::from_utf8(&self.description[..len]).unwrap_or("")
}
}
// ---- Witness type constants for lineage entries ----
/// Witness type: file derivation event.
pub const WITNESS_DERIVATION: u8 = 0x09;
/// Witness type: lineage merge (multi-parent).
pub const WITNESS_LINEAGE_MERGE: u8 = 0x0A;
/// Witness type: lineage snapshot.
pub const WITNESS_LINEAGE_SNAPSHOT: u8 = 0x0B;
/// Witness type: lineage transform.
pub const WITNESS_LINEAGE_TRANSFORM: u8 = 0x0C;
/// Witness type: lineage verification.
pub const WITNESS_LINEAGE_VERIFY: u8 = 0x0D;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_identity_size() {
assert_eq!(core::mem::size_of::<FileIdentity>(), 68);
}
#[test]
fn file_identity_fits_in_reserved() {
// Level0Root reserved area is 252 bytes; FileIdentity is 68 bytes
assert!(core::mem::size_of::<FileIdentity>() <= 252);
}
#[test]
fn file_identity_root() {
let id = [0x42u8; 16];
let fi = FileIdentity::new_root(id);
assert!(fi.is_root());
assert_eq!(fi.file_id, id);
assert_eq!(fi.parent_id, [0u8; 16]);
assert_eq!(fi.parent_hash, [0u8; 32]);
assert_eq!(fi.lineage_depth, 0);
}
#[test]
fn file_identity_zeroed_is_root() {
let fi = FileIdentity::zeroed();
assert!(fi.is_root());
}
#[test]
fn file_identity_round_trip() {
let fi = FileIdentity {
file_id: [1u8; 16],
parent_id: [2u8; 16],
parent_hash: [3u8; 32],
lineage_depth: 42,
};
let bytes = fi.to_bytes();
let decoded = FileIdentity::from_bytes(&bytes);
assert_eq!(fi, decoded);
}
#[test]
fn file_identity_non_root() {
let fi = FileIdentity {
file_id: [1u8; 16],
parent_id: [2u8; 16],
parent_hash: [3u8; 32],
lineage_depth: 1,
};
assert!(!fi.is_root());
}
#[test]
fn derivation_type_round_trip() {
let cases: &[(u8, DerivationType)] = &[
(0, DerivationType::Clone),
(1, DerivationType::Filter),
(2, DerivationType::Merge),
(3, DerivationType::Quantize),
(4, DerivationType::Reindex),
(5, DerivationType::Transform),
(6, DerivationType::Snapshot),
(0xFF, DerivationType::UserDefined),
];
for &(raw, expected) in cases {
assert_eq!(DerivationType::try_from(raw), Ok(expected));
assert_eq!(expected as u8, raw);
}
}
#[test]
fn derivation_type_unknown() {
assert_eq!(DerivationType::try_from(7), Err(7));
assert_eq!(DerivationType::try_from(0xFE), Err(0xFE));
}
#[test]
fn lineage_record_description() {
let record = LineageRecord::new(
[1u8; 16],
[2u8; 16],
[3u8; 32],
DerivationType::Filter,
5,
1_000_000_000,
"filtered by category",
);
assert_eq!(record.description_str(), "filtered by category");
assert_eq!(record.description_len, 20);
}
#[test]
fn lineage_record_long_description_truncated() {
let long_desc = "a]".repeat(50); // 100 chars, way over 47
let record = LineageRecord::new(
[0u8; 16],
[0u8; 16],
[0u8; 32],
DerivationType::Clone,
0,
0,
&long_desc,
);
assert_eq!(record.description_len, 47);
}
#[test]
fn witness_type_constants() {
assert_eq!(WITNESS_DERIVATION, 0x09);
assert_eq!(WITNESS_LINEAGE_MERGE, 0x0A);
assert_eq!(WITNESS_LINEAGE_SNAPSHOT, 0x0B);
assert_eq!(WITNESS_LINEAGE_TRANSFORM, 0x0C);
assert_eq!(WITNESS_LINEAGE_VERIFY, 0x0D);
}
}

View File

@@ -0,0 +1,336 @@
//! Level 0 root manifest and hotset pointer types.
//!
//! The root manifest is always the last 4096 bytes of the most recent
//! MANIFEST_SEG. Its fixed size enables instant location via `seek(EOF - 4096)`.
use crate::constants::ROOT_MANIFEST_MAGIC;
/// Inline hotset pointer for HNSW entry points.
///
/// Offset 0x038 in Level0Root.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct EntrypointPtr {
/// Byte offset to the segment containing HNSW entry points.
pub seg_offset: u64,
/// Block offset within that segment.
pub block_offset: u32,
/// Number of entry points.
pub count: u32,
}
/// Inline hotset pointer for top-layer adjacency.
///
/// Offset 0x048 in Level0Root.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct TopLayerPtr {
/// Byte offset to the segment with top-layer adjacency.
pub seg_offset: u64,
/// Block offset within the segment.
pub block_offset: u32,
/// Number of nodes in the top layer.
pub node_count: u32,
}
/// Inline hotset pointer for cluster centroids / pivots.
///
/// Offset 0x058 in Level0Root.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct CentroidPtr {
/// Byte offset to the segment with cluster centroids.
pub seg_offset: u64,
/// Block offset within the segment.
pub block_offset: u32,
/// Number of centroids.
pub count: u32,
}
/// Inline hotset pointer for quantization dictionary.
///
/// Offset 0x068 in Level0Root.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct QuantDictPtr {
/// Byte offset to the quantization dictionary segment.
pub seg_offset: u64,
/// Block offset within the segment.
pub block_offset: u32,
/// Dictionary size in bytes.
pub size: u32,
}
/// Inline hotset pointer for the hot vector cache (HOT_SEG).
///
/// Offset 0x078 in Level0Root.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct HotCachePtr {
/// Byte offset to the HOT_SEG with interleaved hot vectors.
pub seg_offset: u64,
/// Block offset within the segment.
pub block_offset: u32,
/// Number of vectors in the hot cache.
pub vector_count: u32,
}
/// Inline hotset pointer for prefetch hint table.
///
/// Offset 0x088 in Level0Root.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct PrefetchMapPtr {
/// Byte offset to the prefetch hint table.
pub offset: u64,
/// Number of prefetch entries.
pub entries: u32,
/// Padding to align to 16 bytes (matches other hotset pointers).
pub _pad: u32,
}
/// The Level 0 root manifest (exactly 4096 bytes).
///
/// Always located at the last 4096 bytes of the most recent MANIFEST_SEG.
/// Its fixed size enables instant boot: `seek(EOF - 4096)`.
///
/// ## Binary layout
///
/// | Offset | Size | Field |
/// |--------|------|-------|
/// | 0x000 | 4 | magic (0x52564D30 "RVM0") |
/// | 0x004 | 2 | version |
/// | 0x006 | 2 | flags |
/// | 0x008 | 8 | l1_manifest_offset |
/// | 0x010 | 8 | l1_manifest_length |
/// | 0x018 | 8 | total_vector_count |
/// | 0x020 | 2 | dimension |
/// | 0x022 | 1 | base_dtype |
/// | 0x023 | 1 | profile_id |
/// | 0x024 | 4 | epoch |
/// | 0x028 | 8 | created_ns |
/// | 0x030 | 8 | modified_ns |
/// | 0x038 | 16 | entrypoint_ptr |
/// | 0x048 | 16 | toplayer_ptr |
/// | 0x058 | 16 | centroid_ptr |
/// | 0x068 | 16 | quantdict_ptr |
/// | 0x078 | 16 | hot_cache_ptr |
/// | 0x088 | 16 | prefetch_map_ptr (includes 4B padding) |
/// | 0x098 | 2 | sig_algo |
/// | 0x09A | 2 | sig_length |
/// | 0x09C | 3684 | signature_buf |
/// | 0xF00 | 252 | reserved |
/// | 0xFFC | 4 | root_checksum |
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct Level0Root {
// ---- Basic header (0x000 - 0x037) ----
/// Magic number: must be `0x52564D30` ("RVM0").
pub magic: u32,
/// Root manifest version.
pub version: u16,
/// Root manifest flags.
pub flags: u16,
/// Byte offset to the Level 1 manifest segment.
pub l1_manifest_offset: u64,
/// Byte length of the Level 1 manifest segment.
pub l1_manifest_length: u64,
/// Total vectors across all segments.
pub total_vector_count: u64,
/// Vector dimensionality.
pub dimension: u16,
/// Base data type enum (see `DataType`).
pub base_dtype: u8,
/// Domain profile id.
pub profile_id: u8,
/// Current overlay epoch number.
pub epoch: u32,
/// File creation timestamp (nanoseconds).
pub created_ns: u64,
/// Last modification timestamp (nanoseconds).
pub modified_ns: u64,
// ---- Hotset pointers (0x038 - 0x093) ----
/// HNSW entry points.
pub entrypoint: EntrypointPtr,
/// Top-layer adjacency.
pub toplayer: TopLayerPtr,
/// Cluster centroids / pivots.
pub centroid: CentroidPtr,
/// Quantization dictionary.
pub quantdict: QuantDictPtr,
/// Hot vector cache (HOT_SEG).
pub hot_cache: HotCachePtr,
/// Prefetch hint table.
pub prefetch_map: PrefetchMapPtr,
// ---- Crypto (0x094 - 0x097 + signature) ----
/// Manifest signature algorithm.
pub sig_algo: u16,
/// Signature byte length.
pub sig_length: u16,
/// Signature bytes (up to 3688 bytes; only first `sig_length` are meaningful).
pub signature_buf: [u8; Self::SIG_BUF_SIZE],
// ---- Reserved + checksum (0xF00 - 0xFFF) ----
/// Reserved / zero-padded area.
pub reserved: [u8; 252],
/// CRC32C of bytes 0x000 through 0xFFB.
pub root_checksum: u32,
}
// Compile-time assertion: Level0Root must be exactly 4096 bytes.
const _: () = assert!(core::mem::size_of::<Level0Root>() == 4096);
impl Level0Root {
/// Size of the signature buffer within the root manifest.
/// From offset 0x09C to 0xEFF inclusive = 3684 bytes.
pub const SIG_BUF_SIZE: usize = 3684;
/// Create a zeroed root manifest with only the magic set.
pub const fn zeroed() -> Self {
Self {
magic: ROOT_MANIFEST_MAGIC,
version: 0,
flags: 0,
l1_manifest_offset: 0,
l1_manifest_length: 0,
total_vector_count: 0,
dimension: 0,
base_dtype: 0,
profile_id: 0,
epoch: 0,
created_ns: 0,
modified_ns: 0,
entrypoint: EntrypointPtr {
seg_offset: 0,
block_offset: 0,
count: 0,
},
toplayer: TopLayerPtr {
seg_offset: 0,
block_offset: 0,
node_count: 0,
},
centroid: CentroidPtr {
seg_offset: 0,
block_offset: 0,
count: 0,
},
quantdict: QuantDictPtr {
seg_offset: 0,
block_offset: 0,
size: 0,
},
hot_cache: HotCachePtr {
seg_offset: 0,
block_offset: 0,
vector_count: 0,
},
prefetch_map: PrefetchMapPtr {
offset: 0,
entries: 0,
_pad: 0,
},
sig_algo: 0,
sig_length: 0,
signature_buf: [0u8; Self::SIG_BUF_SIZE],
reserved: [0u8; 252],
root_checksum: 0,
}
}
/// Check whether the magic field matches the expected value.
#[inline]
pub const fn is_valid_magic(&self) -> bool {
self.magic == ROOT_MANIFEST_MAGIC
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn level0_root_size_is_4096() {
assert_eq!(core::mem::size_of::<Level0Root>(), 4096);
}
#[test]
fn zeroed_has_valid_magic() {
let root = Level0Root::zeroed();
assert!(root.is_valid_magic());
}
#[test]
fn field_offsets() {
let root = Level0Root::zeroed();
let base = core::ptr::addr_of!(root) as usize;
// Use addr_of! for packed struct fields to avoid UB.
let magic_off = core::ptr::addr_of!(root.magic) as usize - base;
let version_off = core::ptr::addr_of!(root.version) as usize - base;
let flags_off = core::ptr::addr_of!(root.flags) as usize - base;
let l1_offset_off = core::ptr::addr_of!(root.l1_manifest_offset) as usize - base;
let l1_length_off = core::ptr::addr_of!(root.l1_manifest_length) as usize - base;
let total_vec_off = core::ptr::addr_of!(root.total_vector_count) as usize - base;
let dim_off = core::ptr::addr_of!(root.dimension) as usize - base;
let dtype_off = core::ptr::addr_of!(root.base_dtype) as usize - base;
let profile_off = core::ptr::addr_of!(root.profile_id) as usize - base;
let epoch_off = core::ptr::addr_of!(root.epoch) as usize - base;
let created_off = core::ptr::addr_of!(root.created_ns) as usize - base;
let modified_off = core::ptr::addr_of!(root.modified_ns) as usize - base;
let entry_off = core::ptr::addr_of!(root.entrypoint) as usize - base;
let toplayer_off = core::ptr::addr_of!(root.toplayer) as usize - base;
let centroid_off = core::ptr::addr_of!(root.centroid) as usize - base;
let quantdict_off = core::ptr::addr_of!(root.quantdict) as usize - base;
let hot_cache_off = core::ptr::addr_of!(root.hot_cache) as usize - base;
let prefetch_off = core::ptr::addr_of!(root.prefetch_map) as usize - base;
let sig_algo_off = core::ptr::addr_of!(root.sig_algo) as usize - base;
let sig_len_off = core::ptr::addr_of!(root.sig_length) as usize - base;
let sig_buf_off = core::ptr::addr_of!(root.signature_buf) as usize - base;
let reserved_off = core::ptr::addr_of!(root.reserved) as usize - base;
let checksum_off = core::ptr::addr_of!(root.root_checksum) as usize - base;
assert_eq!(magic_off, 0x000);
assert_eq!(version_off, 0x004);
assert_eq!(flags_off, 0x006);
assert_eq!(l1_offset_off, 0x008);
assert_eq!(l1_length_off, 0x010);
assert_eq!(total_vec_off, 0x018);
assert_eq!(dim_off, 0x020);
assert_eq!(dtype_off, 0x022);
assert_eq!(profile_off, 0x023);
assert_eq!(epoch_off, 0x024);
assert_eq!(created_off, 0x028);
assert_eq!(modified_off, 0x030);
assert_eq!(entry_off, 0x038);
assert_eq!(toplayer_off, 0x048);
assert_eq!(centroid_off, 0x058);
assert_eq!(quantdict_off, 0x068);
assert_eq!(hot_cache_off, 0x078);
assert_eq!(prefetch_off, 0x088);
assert_eq!(sig_algo_off, 0x098);
assert_eq!(sig_len_off, 0x09A);
assert_eq!(sig_buf_off, 0x09C);
assert_eq!(reserved_off, 0xF00);
assert_eq!(checksum_off, 0xFFC);
}
#[test]
fn hotset_pointer_sizes() {
assert_eq!(core::mem::size_of::<EntrypointPtr>(), 16);
assert_eq!(core::mem::size_of::<TopLayerPtr>(), 16);
assert_eq!(core::mem::size_of::<CentroidPtr>(), 16);
assert_eq!(core::mem::size_of::<QuantDictPtr>(), 16);
assert_eq!(core::mem::size_of::<HotCachePtr>(), 16);
assert_eq!(core::mem::size_of::<PrefetchMapPtr>(), 16);
}
}

View File

@@ -0,0 +1,277 @@
//! MEMBERSHIP_SEG (0x22) types for the RVF computational container.
//!
//! Defines the 96-byte `MembershipHeader` and associated enums per ADR-031.
//! The MEMBERSHIP_SEG stores vector membership filters for branches,
//! tracking which vectors belong to a given snapshot or branch.
use crate::error::RvfError;
/// Magic number for `MembershipHeader`: "RVMB" in big-endian.
pub const MEMBERSHIP_MAGIC: u32 = 0x5256_4D42;
/// Filter storage type.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum FilterType {
/// Dense bitmap (one bit per vector).
Bitmap = 0,
/// Roaring bitmap (compressed sparse).
RoaringBitmap = 1,
}
impl TryFrom<u8> for FilterType {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Bitmap),
1 => Ok(Self::RoaringBitmap),
_ => Err(RvfError::InvalidEnumValue {
type_name: "FilterType",
value: value as u64,
}),
}
}
}
/// Filter mode: include-by-default or exclude-by-default.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum FilterMode {
/// Vectors are included unless filtered out.
Include = 0,
/// Vectors are excluded unless explicitly included.
Exclude = 1,
}
impl TryFrom<u8> for FilterMode {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Include),
1 => Ok(Self::Exclude),
_ => Err(RvfError::InvalidEnumValue {
type_name: "FilterMode",
value: value as u64,
}),
}
}
}
/// 96-byte header for MEMBERSHIP_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MembershipHeader {
/// Magic: `MEMBERSHIP_MAGIC` (0x52564D42, "RVMB").
pub magic: u32,
/// MembershipHeader format version (currently 1).
pub version: u16,
/// Filter storage type (see `FilterType`).
pub filter_type: u8,
/// Filter mode (see `FilterMode`).
pub filter_mode: u8,
/// Total number of vectors in the dataset.
pub vector_count: u64,
/// Number of vectors that are members.
pub member_count: u64,
/// Offset to the membership filter within the segment payload.
pub filter_offset: u64,
/// Size of the membership filter in bytes.
pub filter_size: u32,
/// Generation counter for optimistic concurrency.
pub generation_id: u32,
/// SHAKE-256-256 hash of the filter data.
pub filter_hash: [u8; 32],
/// Offset to optional Bloom filter for fast negative lookups.
pub bloom_offset: u64,
/// Size of the Bloom filter in bytes.
pub bloom_size: u32,
/// Reserved (must be zero).
pub _reserved: u32,
/// Reserved (must be zero).
pub _reserved2: [u8; 8],
}
// Compile-time assertion: MembershipHeader must be exactly 96 bytes.
const _: () = assert!(core::mem::size_of::<MembershipHeader>() == 96);
impl MembershipHeader {
/// Serialize the header to a 96-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 96] {
let mut buf = [0u8; 96];
buf[0x00..0x04].copy_from_slice(&self.magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.version.to_le_bytes());
buf[0x06] = self.filter_type;
buf[0x07] = self.filter_mode;
buf[0x08..0x10].copy_from_slice(&self.vector_count.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&self.member_count.to_le_bytes());
buf[0x18..0x20].copy_from_slice(&self.filter_offset.to_le_bytes());
buf[0x20..0x24].copy_from_slice(&self.filter_size.to_le_bytes());
buf[0x24..0x28].copy_from_slice(&self.generation_id.to_le_bytes());
buf[0x28..0x48].copy_from_slice(&self.filter_hash);
buf[0x48..0x50].copy_from_slice(&self.bloom_offset.to_le_bytes());
buf[0x50..0x54].copy_from_slice(&self.bloom_size.to_le_bytes());
buf[0x54..0x58].copy_from_slice(&self._reserved.to_le_bytes());
buf[0x58..0x60].copy_from_slice(&self._reserved2);
buf
}
/// Deserialize a `MembershipHeader` from a 96-byte slice.
pub fn from_bytes(data: &[u8; 96]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != MEMBERSHIP_MAGIC {
return Err(RvfError::BadMagic {
expected: MEMBERSHIP_MAGIC,
got: magic,
});
}
Ok(Self {
magic,
version: u16::from_le_bytes([data[0x04], data[0x05]]),
filter_type: data[0x06],
filter_mode: data[0x07],
vector_count: u64::from_le_bytes([
data[0x08], data[0x09], data[0x0A], data[0x0B], data[0x0C], data[0x0D], data[0x0E],
data[0x0F],
]),
member_count: u64::from_le_bytes([
data[0x10], data[0x11], data[0x12], data[0x13], data[0x14], data[0x15], data[0x16],
data[0x17],
]),
filter_offset: u64::from_le_bytes([
data[0x18], data[0x19], data[0x1A], data[0x1B], data[0x1C], data[0x1D], data[0x1E],
data[0x1F],
]),
filter_size: u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]),
generation_id: u32::from_le_bytes([data[0x24], data[0x25], data[0x26], data[0x27]]),
filter_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x28..0x48]);
h
},
bloom_offset: u64::from_le_bytes([
data[0x48], data[0x49], data[0x4A], data[0x4B], data[0x4C], data[0x4D], data[0x4E],
data[0x4F],
]),
bloom_size: u32::from_le_bytes([data[0x50], data[0x51], data[0x52], data[0x53]]),
_reserved: u32::from_le_bytes([data[0x54], data[0x55], data[0x56], data[0x57]]),
_reserved2: {
let mut r = [0u8; 8];
r.copy_from_slice(&data[0x58..0x60]);
r
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> MembershipHeader {
MembershipHeader {
magic: MEMBERSHIP_MAGIC,
version: 1,
filter_type: FilterType::Bitmap as u8,
filter_mode: FilterMode::Include as u8,
vector_count: 1_000_000,
member_count: 500_000,
filter_offset: 96,
filter_size: 125_000,
generation_id: 1,
filter_hash: [0xCC; 32],
bloom_offset: 0,
bloom_size: 0,
_reserved: 0,
_reserved2: [0; 8],
}
}
#[test]
fn header_size_is_96() {
assert_eq!(core::mem::size_of::<MembershipHeader>(), 96);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = MEMBERSHIP_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVMB");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = MembershipHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.magic, MEMBERSHIP_MAGIC);
assert_eq!(decoded.version, 1);
assert_eq!(decoded.filter_type, FilterType::Bitmap as u8);
assert_eq!(decoded.filter_mode, FilterMode::Include as u8);
assert_eq!(decoded.vector_count, 1_000_000);
assert_eq!(decoded.member_count, 500_000);
assert_eq!(decoded.filter_offset, 96);
assert_eq!(decoded.filter_size, 125_000);
assert_eq!(decoded.generation_id, 1);
assert_eq!(decoded.filter_hash, [0xCC; 32]);
assert_eq!(decoded.bloom_offset, 0);
assert_eq!(decoded.bloom_size, 0);
assert_eq!(decoded._reserved, 0);
assert_eq!(decoded._reserved2, [0; 8]);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = MembershipHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, MEMBERSHIP_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.magic as *const _ as usize - base, 0x00);
assert_eq!(&h.version as *const _ as usize - base, 0x04);
assert_eq!(&h.filter_type as *const _ as usize - base, 0x06);
assert_eq!(&h.filter_mode as *const _ as usize - base, 0x07);
assert_eq!(&h.vector_count as *const _ as usize - base, 0x08);
assert_eq!(&h.member_count as *const _ as usize - base, 0x10);
assert_eq!(&h.filter_offset as *const _ as usize - base, 0x18);
assert_eq!(&h.filter_size as *const _ as usize - base, 0x20);
assert_eq!(&h.generation_id as *const _ as usize - base, 0x24);
assert_eq!(&h.filter_hash as *const _ as usize - base, 0x28);
assert_eq!(&h.bloom_offset as *const _ as usize - base, 0x48);
assert_eq!(&h.bloom_size as *const _ as usize - base, 0x50);
assert_eq!(&h._reserved as *const _ as usize - base, 0x54);
assert_eq!(&h._reserved2 as *const _ as usize - base, 0x58);
}
#[test]
fn filter_type_try_from() {
assert_eq!(FilterType::try_from(0), Ok(FilterType::Bitmap));
assert_eq!(FilterType::try_from(1), Ok(FilterType::RoaringBitmap));
assert!(FilterType::try_from(2).is_err());
assert!(FilterType::try_from(0xFF).is_err());
}
#[test]
fn filter_mode_try_from() {
assert_eq!(FilterMode::try_from(0), Ok(FilterMode::Include));
assert_eq!(FilterMode::try_from(1), Ok(FilterMode::Exclude));
assert!(FilterMode::try_from(2).is_err());
assert!(FilterMode::try_from(0xFF).is_err());
}
}

View File

@@ -0,0 +1,190 @@
//! Hardware and domain profile identifiers.
/// Hardware profile ID (stored in root manifest `profile_id` for hardware tier).
///
/// Determines the runtime behaviour profile (memory budget, tier policy, etc.).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum ProfileId {
/// Generic / minimal profile.
Generic = 0,
/// Core profile (moderate resources).
Core = 1,
/// Hot profile (high-performance, memory-rich).
Hot = 2,
/// Full profile (all features enabled).
Full = 3,
}
impl TryFrom<u8> for ProfileId {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Generic),
1 => Ok(Self::Core),
2 => Ok(Self::Hot),
3 => Ok(Self::Full),
other => Err(other),
}
}
}
/// Domain profile discriminator (semantic overlay on the RVF substrate).
///
/// Stored in the root manifest `profile_id` field and declared in PROFILE_SEG.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum DomainProfile {
/// Generic / unspecified domain.
Generic = 0,
/// Genomics (RVDNA) -- codon, k-mer, motif, structure embeddings.
Rvdna = 1,
/// Language / text (RVText) -- sentence, paragraph, document embeddings.
RvText = 2,
/// Graph / network (RVGraph) -- node, edge, subgraph embeddings.
RvGraph = 3,
/// Vision / imagery (RVVision) -- patch, image, object embeddings.
RvVision = 4,
}
impl DomainProfile {
/// The 4-byte magic number associated with each domain profile.
pub const fn magic(self) -> u32 {
match self {
Self::Generic => 0x0000_0000,
Self::Rvdna => 0x5244_4E41, // "RDNA"
Self::RvText => 0x5254_5854, // "RTXT"
Self::RvGraph => 0x5247_5248, // "RGRH"
Self::RvVision => 0x5256_4953, // "RVIS"
}
}
}
impl DomainProfile {
/// The canonical file extension for this domain profile.
pub const fn extension(&self) -> &'static str {
match self {
Self::Generic => "rvf",
Self::Rvdna => "rvdna",
Self::RvText => "rvtext",
Self::RvGraph => "rvgraph",
Self::RvVision => "rvvis",
}
}
/// Look up a domain profile from a file extension (case-insensitive).
pub fn from_extension(ext: &str) -> Option<Self> {
// Manual case-insensitive comparison for no_std compatibility
let ext_bytes = ext.as_bytes();
if eq_ignore_ascii_case(ext_bytes, b"rvf") {
Some(Self::Generic)
} else if eq_ignore_ascii_case(ext_bytes, b"rvdna") {
Some(Self::Rvdna)
} else if eq_ignore_ascii_case(ext_bytes, b"rvtext") {
Some(Self::RvText)
} else if eq_ignore_ascii_case(ext_bytes, b"rvgraph") {
Some(Self::RvGraph)
} else if eq_ignore_ascii_case(ext_bytes, b"rvvis") {
Some(Self::RvVision)
} else {
None
}
}
}
/// Case-insensitive ASCII byte comparison.
fn eq_ignore_ascii_case(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter()
.zip(b.iter())
.all(|(x, y)| x.eq_ignore_ascii_case(y))
}
impl TryFrom<u8> for DomainProfile {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Generic),
1 => Ok(Self::Rvdna),
2 => Ok(Self::RvText),
3 => Ok(Self::RvGraph),
4 => Ok(Self::RvVision),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profile_id_round_trip() {
for raw in 0..=3u8 {
let p = ProfileId::try_from(raw).unwrap();
assert_eq!(p as u8, raw);
}
assert_eq!(ProfileId::try_from(4), Err(4));
}
#[test]
fn domain_profile_round_trip() {
for raw in 0..=4u8 {
let d = DomainProfile::try_from(raw).unwrap();
assert_eq!(d as u8, raw);
}
assert_eq!(DomainProfile::try_from(5), Err(5));
}
#[test]
fn domain_extension_round_trip() {
let profiles = [
DomainProfile::Generic,
DomainProfile::Rvdna,
DomainProfile::RvText,
DomainProfile::RvGraph,
DomainProfile::RvVision,
];
for p in profiles {
let ext = p.extension();
let back = DomainProfile::from_extension(ext).unwrap();
assert_eq!(back, p, "round-trip failed for {ext}");
}
}
#[test]
fn domain_extension_case_insensitive() {
assert_eq!(
DomainProfile::from_extension("RVDNA"),
Some(DomainProfile::Rvdna)
);
assert_eq!(
DomainProfile::from_extension("RvF"),
Some(DomainProfile::Generic)
);
assert_eq!(
DomainProfile::from_extension("RvText"),
Some(DomainProfile::RvText)
);
}
#[test]
fn domain_extension_unknown() {
assert_eq!(DomainProfile::from_extension("txt"), None);
assert_eq!(DomainProfile::from_extension(""), None);
}
#[test]
fn domain_magic_values() {
assert_eq!(&DomainProfile::Rvdna.magic().to_be_bytes(), b"RDNA");
assert_eq!(&DomainProfile::RvText.magic().to_be_bytes(), b"RTXT");
assert_eq!(&DomainProfile::RvGraph.magic().to_be_bytes(), b"RGRH");
assert_eq!(&DomainProfile::RvVision.magic().to_be_bytes(), b"RVIS");
}
}

View File

@@ -0,0 +1,378 @@
//! QR Cognitive Seed types for ADR-034.
//!
//! Defines the RVQS (RuVector QR Seed) binary format — a compact
//! self-bootstrapping cognitive payload that fits in a single QR code.
//! Scan and mount a portable brain.
/// RVQS magic: "RVQS" in ASCII = 0x52565153.
pub const SEED_MAGIC: u32 = 0x5256_5153;
/// Maximum payload that fits in QR Version 40, Low EC.
pub const QR_MAX_BYTES: usize = 2_953;
// ---- Seed Flags (bit positions) ----
/// Embedded WASM microkernel present.
pub const SEED_HAS_MICROKERNEL: u16 = 0x0001;
/// Progressive download manifest present.
pub const SEED_HAS_DOWNLOAD: u16 = 0x0002;
/// Payload is signed.
pub const SEED_SIGNED: u16 = 0x0004;
/// Seed is useful without network access.
pub const SEED_OFFLINE_CAPABLE: u16 = 0x0008;
/// Payload is encrypted.
pub const SEED_ENCRYPTED: u16 = 0x0010;
/// Microkernel is Brotli-compressed.
pub const SEED_COMPRESSED: u16 = 0x0020;
/// Seed contains inline vector data.
pub const SEED_HAS_VECTORS: u16 = 0x0040;
/// Seed can upgrade itself via streaming.
pub const SEED_STREAM_UPGRADE: u16 = 0x0080;
/// Header size in bytes (fixed).
pub const SEED_HEADER_SIZE: usize = 64;
/// RVQS header — the first 64 bytes of any QR Cognitive Seed.
///
/// Contains everything needed to verify and bootstrap the seed
/// before any network access.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct SeedHeader {
/// Magic number: must be SEED_MAGIC.
pub seed_magic: u32,
/// Seed format version.
pub seed_version: u16,
/// Seed flags bitfield.
pub flags: u16,
/// Unique identifier for this seed.
pub file_id: [u8; 8],
/// Expected total vectors when fully loaded.
pub total_vector_count: u32,
/// Vector dimensionality.
pub dimension: u16,
/// Base data type (DataType enum).
pub base_dtype: u8,
/// Domain profile id.
pub profile_id: u8,
/// Seed creation timestamp (nanoseconds since epoch).
pub created_ns: u64,
/// Offset to WASM microkernel data within the seed payload.
pub microkernel_offset: u32,
/// Compressed microkernel size in bytes.
pub microkernel_size: u32,
/// Offset to download manifest within the seed payload.
pub download_manifest_offset: u32,
/// Download manifest size in bytes.
pub download_manifest_size: u32,
/// Signature algorithm (0=Ed25519, 1=ML-DSA-65).
pub sig_algo: u16,
/// Signature byte length.
pub sig_length: u16,
/// Total seed payload size in bytes.
pub total_seed_size: u32,
/// SHAKE-256-64 of the complete expanded RVF file.
pub content_hash: [u8; 8],
}
const _: () = assert!(core::mem::size_of::<SeedHeader>() == SEED_HEADER_SIZE);
impl SeedHeader {
/// Check if the magic field is valid.
pub const fn is_valid_magic(&self) -> bool {
self.seed_magic == SEED_MAGIC
}
/// Check if the seed has an embedded microkernel.
pub const fn has_microkernel(&self) -> bool {
self.flags & SEED_HAS_MICROKERNEL != 0
}
/// Check if the seed has a download manifest.
pub const fn has_download_manifest(&self) -> bool {
self.flags & SEED_HAS_DOWNLOAD != 0
}
/// Check if the seed is signed.
pub const fn is_signed(&self) -> bool {
self.flags & SEED_SIGNED != 0
}
/// Check if the seed is offline-capable.
pub const fn is_offline_capable(&self) -> bool {
self.flags & SEED_OFFLINE_CAPABLE != 0
}
/// Check if the seed fits in a single QR code.
pub const fn fits_in_qr(&self) -> bool {
(self.total_seed_size as usize) <= QR_MAX_BYTES
}
/// Serialize the header to 64 bytes (little-endian).
pub fn to_bytes(&self) -> [u8; SEED_HEADER_SIZE] {
let mut buf = [0u8; SEED_HEADER_SIZE];
buf[0x00..0x04].copy_from_slice(&self.seed_magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.seed_version.to_le_bytes());
buf[0x06..0x08].copy_from_slice(&self.flags.to_le_bytes());
buf[0x08..0x10].copy_from_slice(&self.file_id);
buf[0x10..0x14].copy_from_slice(&self.total_vector_count.to_le_bytes());
buf[0x14..0x16].copy_from_slice(&self.dimension.to_le_bytes());
buf[0x16] = self.base_dtype;
buf[0x17] = self.profile_id;
buf[0x18..0x20].copy_from_slice(&self.created_ns.to_le_bytes());
buf[0x20..0x24].copy_from_slice(&self.microkernel_offset.to_le_bytes());
buf[0x24..0x28].copy_from_slice(&self.microkernel_size.to_le_bytes());
buf[0x28..0x2C].copy_from_slice(&self.download_manifest_offset.to_le_bytes());
buf[0x2C..0x30].copy_from_slice(&self.download_manifest_size.to_le_bytes());
buf[0x30..0x32].copy_from_slice(&self.sig_algo.to_le_bytes());
buf[0x32..0x34].copy_from_slice(&self.sig_length.to_le_bytes());
buf[0x34..0x38].copy_from_slice(&self.total_seed_size.to_le_bytes());
buf[0x38..0x40].copy_from_slice(&self.content_hash);
buf
}
/// Deserialize from 64 bytes (little-endian).
pub fn from_bytes(buf: &[u8]) -> Result<Self, crate::error::RvfError> {
if buf.len() < SEED_HEADER_SIZE {
return Err(crate::error::RvfError::SizeMismatch {
expected: SEED_HEADER_SIZE,
got: buf.len(),
});
}
let seed_magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
if seed_magic != SEED_MAGIC {
return Err(crate::error::RvfError::BadMagic {
expected: SEED_MAGIC,
got: seed_magic,
});
}
let mut file_id = [0u8; 8];
file_id.copy_from_slice(&buf[0x08..0x10]);
let mut content_hash = [0u8; 8];
content_hash.copy_from_slice(&buf[0x38..0x40]);
Ok(Self {
seed_magic,
seed_version: u16::from_le_bytes([buf[0x04], buf[0x05]]),
flags: u16::from_le_bytes([buf[0x06], buf[0x07]]),
file_id,
total_vector_count: u32::from_le_bytes([buf[0x10], buf[0x11], buf[0x12], buf[0x13]]),
dimension: u16::from_le_bytes([buf[0x14], buf[0x15]]),
base_dtype: buf[0x16],
profile_id: buf[0x17],
created_ns: u64::from_le_bytes([
buf[0x18], buf[0x19], buf[0x1A], buf[0x1B], buf[0x1C], buf[0x1D], buf[0x1E],
buf[0x1F],
]),
microkernel_offset: u32::from_le_bytes([buf[0x20], buf[0x21], buf[0x22], buf[0x23]]),
microkernel_size: u32::from_le_bytes([buf[0x24], buf[0x25], buf[0x26], buf[0x27]]),
download_manifest_offset: u32::from_le_bytes([
buf[0x28], buf[0x29], buf[0x2A], buf[0x2B],
]),
download_manifest_size: u32::from_le_bytes([
buf[0x2C], buf[0x2D], buf[0x2E], buf[0x2F],
]),
sig_algo: u16::from_le_bytes([buf[0x30], buf[0x31]]),
sig_length: u16::from_le_bytes([buf[0x32], buf[0x33]]),
total_seed_size: u32::from_le_bytes([buf[0x34], buf[0x35], buf[0x36], buf[0x37]]),
content_hash,
})
}
}
// ---- Download Manifest TLV Tags ----
/// Primary download host.
pub const DL_TAG_HOST_PRIMARY: u16 = 0x0001;
/// Fallback download host.
pub const DL_TAG_HOST_FALLBACK: u16 = 0x0002;
/// SHAKE-256-256 hash of the full RVF file.
pub const DL_TAG_CONTENT_HASH: u16 = 0x0003;
/// Expected total file size.
pub const DL_TAG_TOTAL_SIZE: u16 = 0x0004;
/// Progressive layer manifest.
pub const DL_TAG_LAYER_MANIFEST: u16 = 0x0005;
/// Ephemeral session token.
pub const DL_TAG_SESSION_TOKEN: u16 = 0x0006;
/// Token TTL in seconds.
pub const DL_TAG_TTL: u16 = 0x0007;
/// TLS certificate pin (SHA-256 of SPKI).
pub const DL_TAG_CERT_PIN: u16 = 0x0008;
/// A single host entry in the download manifest.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HostEntry {
/// Download URL (HTTPS).
pub url: [u8; 128],
/// Actual URL length within the buffer.
pub url_length: u16,
/// Priority (lower = preferred).
pub priority: u16,
/// Geographic region hint.
pub region: u16,
/// SHAKE-256-128 of host's public key.
pub host_key_hash: [u8; 16],
}
impl HostEntry {
/// Get the URL as a string slice.
pub fn url_str(&self) -> Option<&str> {
core::str::from_utf8(&self.url[..self.url_length as usize]).ok()
}
/// Encode to bytes.
pub fn to_bytes(&self) -> [u8; 150] {
let mut buf = [0u8; 150];
buf[0..2].copy_from_slice(&self.url_length.to_le_bytes());
buf[2..130].copy_from_slice(&self.url);
buf[130..132].copy_from_slice(&self.priority.to_le_bytes());
buf[132..134].copy_from_slice(&self.region.to_le_bytes());
buf[134..150].copy_from_slice(&self.host_key_hash);
buf
}
}
/// A single layer entry in the progressive download manifest.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct LayerEntry {
/// Byte offset in the full RVF file.
pub offset: u32,
/// Layer size in bytes.
pub size: u32,
/// SHAKE-256-128 content hash.
pub content_hash: [u8; 16],
/// Layer identifier.
pub layer_id: u8,
/// Download priority (0 = immediate).
pub priority: u8,
/// 1 = required before first query.
pub required: u8,
/// Padding.
pub _pad: u8,
}
const _: () = assert!(core::mem::size_of::<LayerEntry>() == 28);
/// Well-known layer identifiers.
pub mod layer_id {
/// Level 0 manifest (4 KB).
pub const LEVEL0: u8 = 0;
/// Hot cache (centroids + entry points).
pub const HOT_CACHE: u8 = 1;
/// HNSW Layer A (recall >= 0.70).
pub const HNSW_LAYER_A: u8 = 2;
/// Quantization dictionaries.
pub const QUANT_DICT: u8 = 3;
/// HNSW Layer B (recall >= 0.85).
pub const HNSW_LAYER_B: u8 = 4;
/// Full vectors (warm tier).
pub const FULL_VECTORS: u8 = 5;
/// HNSW Layer C (recall >= 0.95).
pub const HNSW_LAYER_C: u8 = 6;
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
fn test_header() -> SeedHeader {
SeedHeader {
seed_magic: SEED_MAGIC,
seed_version: 1,
flags: SEED_HAS_MICROKERNEL
| SEED_HAS_DOWNLOAD
| SEED_SIGNED
| SEED_COMPRESSED
| SEED_STREAM_UPGRADE,
file_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
total_vector_count: 100_000,
dimension: 384,
base_dtype: 1, // F16
profile_id: 2, // Hot
created_ns: 1_700_000_000_000_000_000,
microkernel_offset: 64,
microkernel_size: 2100,
download_manifest_offset: 2164,
download_manifest_size: 512,
sig_algo: 0, // Ed25519
sig_length: 64,
total_seed_size: 2740,
content_hash: [0xAB; 8],
}
}
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<SeedHeader>(), 64);
}
#[test]
fn layer_entry_size_is_28() {
assert_eq!(core::mem::size_of::<LayerEntry>(), 28);
}
#[test]
fn header_round_trip() {
let header = test_header();
let bytes = header.to_bytes();
let decoded = SeedHeader::from_bytes(&bytes).unwrap();
assert_eq!(header, decoded);
}
#[test]
fn header_invalid_magic() {
let mut bytes = test_header().to_bytes();
bytes[0] = 0xFF; // Corrupt magic.
assert!(SeedHeader::from_bytes(&bytes).is_err());
}
#[test]
fn header_too_short() {
let bytes = [0u8; 32];
assert!(SeedHeader::from_bytes(&bytes).is_err());
}
#[test]
fn header_flags() {
let header = test_header();
assert!(header.has_microkernel());
assert!(header.has_download_manifest());
assert!(header.is_signed());
assert!(!header.is_offline_capable());
assert!(header.fits_in_qr());
}
#[test]
fn header_fits_in_qr() {
let mut header = test_header();
header.total_seed_size = 2953; // Max QR capacity.
assert!(header.fits_in_qr());
header.total_seed_size = 2954;
assert!(!header.fits_in_qr());
}
#[test]
fn seed_magic_is_rvqs() {
let bytes = SEED_MAGIC.to_be_bytes();
assert_eq!(&bytes, b"RVQS");
}
#[test]
fn flag_bit_positions() {
assert_eq!(SEED_HAS_MICROKERNEL, 1 << 0);
assert_eq!(SEED_HAS_DOWNLOAD, 1 << 1);
assert_eq!(SEED_SIGNED, 1 << 2);
assert_eq!(SEED_OFFLINE_CAPABLE, 1 << 3);
assert_eq!(SEED_ENCRYPTED, 1 << 4);
assert_eq!(SEED_COMPRESSED, 1 << 5);
assert_eq!(SEED_HAS_VECTORS, 1 << 6);
assert_eq!(SEED_STREAM_UPGRADE, 1 << 7);
}
}

View File

@@ -0,0 +1,414 @@
//! Quality envelope types for ADR-033 progressive indexing hardening.
//!
//! Defines the mandatory outer return type (`QualityEnvelope`) for all query
//! APIs, along with retrieval-level and response-level quality signals,
//! budget reporting, and degradation diagnostics.
/// Quality confidence for a single retrieval candidate.
///
/// Attached per-candidate during the search pipeline. Internal use only;
/// consumers see `ResponseQuality` via the `QualityEnvelope`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum RetrievalQuality {
/// Full index traversed, high confidence in candidate set.
Full = 0x00,
/// Partial index (Layer A+B), good confidence.
Partial = 0x01,
/// Layer A only, moderate confidence.
LayerAOnly = 0x02,
/// Degenerate distribution detected, low confidence.
DegenerateDetected = 0x03,
/// Brute-force fallback used within budget, exact over scanned region.
BruteForceBudgeted = 0x04,
}
/// Response-level quality signal returned to the caller at the API boundary.
///
/// This is the field that consumers (RAG pipelines, agent tool chains,
/// MCP clients) **must** inspect before using results.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum ResponseQuality {
/// All results from full index. Trust fully.
Verified = 0x00,
/// Results from partial index. Usable but may miss neighbors.
Usable = 0x01,
/// Degraded retrieval detected. Results are best-effort.
Degraded = 0x02,
/// Insufficient candidates found. Results are unreliable.
Unreliable = 0x03,
}
/// Caller hint for quality vs latency trade-off.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum QualityPreference {
/// Runtime decides. Default. Fastest path that meets internal thresholds.
Auto = 0x00,
/// Caller prefers quality over latency. Runtime may widen n_probe,
/// extend budgets up to 4x, and block until Layer B loads.
PreferQuality = 0x01,
/// Caller prefers latency over quality. Runtime may skip safety net,
/// reduce n_probe. ResponseQuality honestly reports what it gets.
PreferLatency = 0x02,
/// Caller explicitly accepts degraded results. Required to proceed
/// when ResponseQuality would be Degraded or Unreliable under Auto.
AcceptDegraded = 0x03,
}
impl Default for QualityPreference {
fn default() -> Self {
Self::Auto
}
}
/// Which index layers were available and used during a query.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IndexLayersUsed {
pub layer_a: bool,
pub layer_b: bool,
pub layer_c: bool,
pub hot_cache: bool,
}
/// Evidence chain: what index state was actually used for a query.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SearchEvidenceSummary {
/// Which index layers were available and used.
pub layers_used: IndexLayersUsed,
/// Effective n_probe (after any adaptive widening).
pub n_probe_effective: u32,
/// Whether degenerate distribution was detected.
pub degenerate_detected: bool,
/// Coefficient of variation of top-K centroid distances.
pub centroid_distance_cv: f32,
/// Number of candidates found by HNSW before safety net.
pub hnsw_candidate_count: u32,
/// Number of candidates added by safety net scan.
pub safety_net_candidate_count: u32,
}
impl Default for SearchEvidenceSummary {
fn default() -> Self {
Self {
layers_used: IndexLayersUsed::default(),
n_probe_effective: 0,
degenerate_detected: false,
centroid_distance_cv: 0.0,
hnsw_candidate_count: 0,
safety_net_candidate_count: 0,
}
}
}
/// Resource consumption report for a single query.
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BudgetReport {
/// Wall-clock time for centroid routing (microseconds).
pub centroid_routing_us: u64,
/// Wall-clock time for HNSW traversal (microseconds).
pub hnsw_traversal_us: u64,
/// Wall-clock time for safety net scan (microseconds).
pub safety_net_scan_us: u64,
/// Wall-clock time for reranking (microseconds).
pub reranking_us: u64,
/// Total wall-clock time (microseconds).
pub total_us: u64,
/// Distance evaluations performed.
pub distance_ops: u64,
/// Distance evaluations budget.
pub distance_ops_budget: u64,
/// Bytes read from storage.
pub bytes_read: u64,
/// Candidates scanned in safety net.
pub linear_scan_count: u64,
/// Candidate scan budget.
pub linear_scan_budget: u64,
}
/// Which fallback path was chosen during query execution.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum FallbackPath {
/// Normal HNSW traversal, no fallback needed.
None = 0x00,
/// Adaptive n_probe widening due to epoch drift.
NProbeWidened = 0x01,
/// Adaptive n_probe widening due to degenerate distribution.
DegenerateWidened = 0x02,
/// Selective safety net scan on hot cache.
SafetyNetSelective = 0x03,
/// Safety net budget exhausted before completion.
SafetyNetBudgetExhausted = 0x04,
}
/// Structured reason for quality degradation.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DegradationReason {
/// Centroid epoch drift exceeded threshold.
CentroidDrift { epoch_drift: u32, max_drift: u32 },
/// Degenerate distance distribution detected.
DegenerateDistribution { cv: f32, threshold: f32 },
/// Budget exhausted during safety net scan.
BudgetExhausted {
scanned: u64,
total: u64,
budget_type: BudgetType,
},
/// Index layer not yet loaded.
IndexNotLoaded { available: IndexLayersUsed },
}
/// Which budget cap was hit.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum BudgetType {
Time = 0x00,
Candidates = 0x01,
DistanceOps = 0x02,
}
/// Why quality is degraded — full diagnostic report.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DegradationReport {
/// Which fallback path was chosen.
pub fallback_path: FallbackPath,
/// Why it was chosen (structured, not prose).
pub reason: DegradationReason,
/// What guarantee is lost relative to Full quality.
pub guarantee_lost: &'static str,
}
/// Budget caps for the brute-force safety net.
///
/// All three are enforced simultaneously. The scan stops at whichever hits
/// first. These are runtime limits, not caller-adjustable above the defaults
/// (unless `QualityPreference::PreferQuality`, which extends to 4x).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SafetyNetBudget {
/// Maximum wall-clock time for the safety net scan (microseconds).
pub max_scan_time_us: u64,
/// Maximum number of candidate vectors to scan.
pub max_scan_candidates: u64,
/// Maximum number of distance evaluations.
pub max_distance_ops: u64,
}
impl SafetyNetBudget {
/// Layer A only defaults: tight budget for instant first query.
pub const LAYER_A: Self = Self {
max_scan_time_us: 2_000, // 2 ms
max_scan_candidates: 10_000,
max_distance_ops: 10_000,
};
/// Partial index defaults: moderate budget.
pub const PARTIAL: Self = Self {
max_scan_time_us: 5_000, // 5 ms
max_scan_candidates: 50_000,
max_distance_ops: 50_000,
};
/// Full index: generous budget.
pub const FULL: Self = Self {
max_scan_time_us: 10_000, // 10 ms
max_scan_candidates: 100_000,
max_distance_ops: 100_000,
};
/// Disabled: all zeros. Safety net will not scan anything.
pub const DISABLED: Self = Self {
max_scan_time_us: 0,
max_scan_candidates: 0,
max_distance_ops: 0,
};
/// Extend all budgets by 4x for PreferQuality mode.
/// Uses saturating arithmetic to prevent overflow.
pub const fn extended_4x(&self) -> Self {
Self {
max_scan_time_us: self.max_scan_time_us.saturating_mul(4),
max_scan_candidates: self.max_scan_candidates.saturating_mul(4),
max_distance_ops: self.max_distance_ops.saturating_mul(4),
}
}
/// Check if all budgets are zero (disabled).
pub const fn is_disabled(&self) -> bool {
self.max_scan_time_us == 0 && self.max_scan_candidates == 0 && self.max_distance_ops == 0
}
}
impl Default for SafetyNetBudget {
fn default() -> Self {
Self::LAYER_A
}
}
/// Derive `ResponseQuality` from the worst `RetrievalQuality` in the result set.
///
/// Empty input returns `Unreliable` — zero results means zero confidence.
pub fn derive_response_quality(retrieval_qualities: &[RetrievalQuality]) -> ResponseQuality {
if retrieval_qualities.is_empty() {
return ResponseQuality::Unreliable;
}
let worst = retrieval_qualities
.iter()
.copied()
.max_by_key(|q| *q as u8)
.unwrap_or(RetrievalQuality::Full);
match worst {
RetrievalQuality::Full => ResponseQuality::Verified,
RetrievalQuality::Partial => ResponseQuality::Usable,
RetrievalQuality::LayerAOnly => ResponseQuality::Usable,
RetrievalQuality::DegenerateDetected => ResponseQuality::Degraded,
RetrievalQuality::BruteForceBudgeted => ResponseQuality::Degraded,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retrieval_quality_ordering() {
assert!(RetrievalQuality::Full < RetrievalQuality::BruteForceBudgeted);
assert!(RetrievalQuality::Partial < RetrievalQuality::DegenerateDetected);
}
#[test]
fn response_quality_ordering() {
assert!(ResponseQuality::Verified < ResponseQuality::Unreliable);
assert!(ResponseQuality::Usable < ResponseQuality::Degraded);
}
#[test]
fn derive_quality_full() {
let q = derive_response_quality(&[RetrievalQuality::Full, RetrievalQuality::Full]);
assert_eq!(q, ResponseQuality::Verified);
}
#[test]
fn derive_quality_mixed() {
let q = derive_response_quality(&[
RetrievalQuality::Full,
RetrievalQuality::DegenerateDetected,
]);
assert_eq!(q, ResponseQuality::Degraded);
}
#[test]
fn derive_quality_empty_is_unreliable() {
let q = derive_response_quality(&[]);
assert_eq!(q, ResponseQuality::Unreliable);
}
#[test]
fn derive_quality_layer_a() {
let q = derive_response_quality(&[RetrievalQuality::LayerAOnly]);
assert_eq!(q, ResponseQuality::Usable);
}
#[test]
fn derive_quality_brute_force() {
let q = derive_response_quality(&[RetrievalQuality::BruteForceBudgeted]);
assert_eq!(q, ResponseQuality::Degraded);
}
#[test]
fn safety_net_budget_layer_a() {
let b = SafetyNetBudget::LAYER_A;
assert_eq!(b.max_scan_time_us, 2_000);
assert_eq!(b.max_scan_candidates, 10_000);
assert_eq!(b.max_distance_ops, 10_000);
assert!(!b.is_disabled());
}
#[test]
fn safety_net_budget_extended() {
let b = SafetyNetBudget::LAYER_A.extended_4x();
assert_eq!(b.max_scan_time_us, 8_000);
assert_eq!(b.max_scan_candidates, 40_000);
assert_eq!(b.max_distance_ops, 40_000);
}
#[test]
fn safety_net_budget_disabled() {
let b = SafetyNetBudget::DISABLED;
assert!(b.is_disabled());
assert_eq!(b.max_scan_time_us, 0);
}
#[test]
fn quality_preference_default_is_auto() {
assert_eq!(QualityPreference::default(), QualityPreference::Auto);
}
#[test]
fn quality_repr_values() {
assert_eq!(RetrievalQuality::Full as u8, 0x00);
assert_eq!(RetrievalQuality::BruteForceBudgeted as u8, 0x04);
assert_eq!(ResponseQuality::Verified as u8, 0x00);
assert_eq!(ResponseQuality::Unreliable as u8, 0x03);
assert_eq!(QualityPreference::Auto as u8, 0x00);
assert_eq!(QualityPreference::AcceptDegraded as u8, 0x03);
}
#[test]
fn fallback_path_repr() {
assert_eq!(FallbackPath::None as u8, 0x00);
assert_eq!(FallbackPath::SafetyNetBudgetExhausted as u8, 0x04);
}
#[test]
fn budget_report_default_is_zero() {
let r = BudgetReport::default();
assert_eq!(r.total_us, 0);
assert_eq!(r.distance_ops, 0);
}
#[test]
fn degradation_report_construction() {
let report = DegradationReport {
fallback_path: FallbackPath::SafetyNetBudgetExhausted,
reason: DegradationReason::BudgetExhausted {
scanned: 5000,
total: 10000,
budget_type: BudgetType::DistanceOps,
},
guarantee_lost: "recall may be below target",
};
assert_eq!(report.fallback_path, FallbackPath::SafetyNetBudgetExhausted);
}
#[test]
fn evidence_summary_default() {
let e = SearchEvidenceSummary::default();
assert!(!e.degenerate_detected);
assert_eq!(e.n_probe_effective, 0);
assert_eq!(e.centroid_distance_cv, 0.0);
}
#[test]
fn index_layers_default_all_false() {
let l = IndexLayersUsed::default();
assert!(!l.layer_a);
assert!(!l.layer_b);
assert!(!l.layer_c);
assert!(!l.hot_cache);
}
}

View File

@@ -0,0 +1,49 @@
//! Quantization type discriminator for QUANT_SEG payloads.
/// Identifies the quantization method stored in a QUANT_SEG.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum QuantType {
/// Scalar quantization (min-max per dimension).
Scalar = 0,
/// Product quantization (codebook per subspace).
Product = 1,
/// Binary threshold quantization (sign bit per dimension).
BinaryThreshold = 2,
/// Residual product quantization.
ResidualPq = 3,
}
impl TryFrom<u8> for QuantType {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Scalar),
1 => Ok(Self::Product),
2 => Ok(Self::BinaryThreshold),
3 => Ok(Self::ResidualPq),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
for raw in 0..=3u8 {
let qt = QuantType::try_from(raw).unwrap();
assert_eq!(qt as u8, raw);
}
}
#[test]
fn invalid_value() {
assert_eq!(QuantType::try_from(4), Err(4));
assert_eq!(QuantType::try_from(255), Err(255));
}
}

View File

@@ -0,0 +1,192 @@
//! REFCOUNT_SEG (0x21) types for the RVF computational container.
//!
//! Defines the 32-byte `RefcountHeader` per ADR-031.
//! The REFCOUNT_SEG tracks reference counts for shared clusters,
//! enabling safe snapshot deletion and garbage collection.
use crate::error::RvfError;
/// Magic number for `RefcountHeader`: "RVRC" in big-endian.
pub const REFCOUNT_MAGIC: u32 = 0x5256_5243;
/// 32-byte header for REFCOUNT_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
/// little-endian on the wire.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct RefcountHeader {
/// Magic: `REFCOUNT_MAGIC` (0x52565243, "RVRC").
pub magic: u32,
/// RefcountHeader format version (currently 1).
pub version: u16,
/// Width of each refcount entry in bytes (1, 2, or 4).
pub refcount_width: u8,
/// Padding (must be zero).
pub _pad: u8,
/// Number of clusters tracked.
pub cluster_count: u32,
/// Maximum refcount value before overflow.
pub max_refcount: u32,
/// Offset to the refcount array within the segment payload.
pub array_offset: u64,
/// Snapshot epoch: 0 = mutable, >0 = frozen at this epoch.
pub snapshot_epoch: u32,
/// Reserved (must be zero).
pub _reserved: u32,
}
// Compile-time assertion: RefcountHeader must be exactly 32 bytes.
const _: () = assert!(core::mem::size_of::<RefcountHeader>() == 32);
impl RefcountHeader {
/// Serialize the header to a 32-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 32] {
let mut buf = [0u8; 32];
buf[0x00..0x04].copy_from_slice(&self.magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.version.to_le_bytes());
buf[0x06] = self.refcount_width;
buf[0x07] = self._pad;
buf[0x08..0x0C].copy_from_slice(&self.cluster_count.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&self.max_refcount.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&self.array_offset.to_le_bytes());
buf[0x18..0x1C].copy_from_slice(&self.snapshot_epoch.to_le_bytes());
buf[0x1C..0x20].copy_from_slice(&self._reserved.to_le_bytes());
buf
}
/// Deserialize a `RefcountHeader` from a 32-byte slice.
pub fn from_bytes(data: &[u8; 32]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != REFCOUNT_MAGIC {
return Err(RvfError::BadMagic {
expected: REFCOUNT_MAGIC,
got: magic,
});
}
let refcount_width = data[0x06];
let pad = data[0x07];
let reserved = u32::from_le_bytes([data[0x1C], data[0x1D], data[0x1E], data[0x1F]]);
// Validate refcount_width is 1, 2, or 4 as specified
if refcount_width != 1 && refcount_width != 2 && refcount_width != 4 {
return Err(RvfError::InvalidEnumValue {
type_name: "RefcountHeader::refcount_width",
value: refcount_width as u64,
});
}
// Validate padding and reserved fields are zero (spec requirement)
if pad != 0 {
return Err(RvfError::InvalidEnumValue {
type_name: "RefcountHeader::_pad",
value: pad as u64,
});
}
if reserved != 0 {
return Err(RvfError::InvalidEnumValue {
type_name: "RefcountHeader::_reserved",
value: reserved as u64,
});
}
Ok(Self {
magic,
version: u16::from_le_bytes([data[0x04], data[0x05]]),
refcount_width,
_pad: pad,
cluster_count: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
max_refcount: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
array_offset: u64::from_le_bytes([
data[0x10], data[0x11], data[0x12], data[0x13], data[0x14], data[0x15], data[0x16],
data[0x17],
]),
snapshot_epoch: u32::from_le_bytes([data[0x18], data[0x19], data[0x1A], data[0x1B]]),
_reserved: reserved,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> RefcountHeader {
RefcountHeader {
magic: REFCOUNT_MAGIC,
version: 1,
refcount_width: 2,
_pad: 0,
cluster_count: 1024,
max_refcount: 65535,
array_offset: 64,
snapshot_epoch: 0,
_reserved: 0,
}
}
#[test]
fn header_size_is_32() {
assert_eq!(core::mem::size_of::<RefcountHeader>(), 32);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = REFCOUNT_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVRC");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = RefcountHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.magic, REFCOUNT_MAGIC);
assert_eq!(decoded.version, 1);
assert_eq!(decoded.refcount_width, 2);
assert_eq!(decoded._pad, 0);
assert_eq!(decoded.cluster_count, 1024);
assert_eq!(decoded.max_refcount, 65535);
assert_eq!(decoded.array_offset, 64);
assert_eq!(decoded.snapshot_epoch, 0);
assert_eq!(decoded._reserved, 0);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00; // corrupt magic
let err = RefcountHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, REFCOUNT_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn field_offsets() {
let h = sample_header();
let base = &h as *const _ as usize;
assert_eq!(&h.magic as *const _ as usize - base, 0x00);
assert_eq!(&h.version as *const _ as usize - base, 0x04);
assert_eq!(&h.refcount_width as *const _ as usize - base, 0x06);
assert_eq!(&h._pad as *const _ as usize - base, 0x07);
assert_eq!(&h.cluster_count as *const _ as usize - base, 0x08);
assert_eq!(&h.max_refcount as *const _ as usize - base, 0x0C);
assert_eq!(&h.array_offset as *const _ as usize - base, 0x10);
assert_eq!(&h.snapshot_epoch as *const _ as usize - base, 0x18);
assert_eq!(&h._reserved as *const _ as usize - base, 0x1C);
}
#[test]
fn frozen_snapshot_epoch() {
let mut h = sample_header();
h.snapshot_epoch = 42;
let bytes = h.to_bytes();
let decoded = RefcountHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.snapshot_epoch, 42);
}
}

View File

@@ -0,0 +1,408 @@
//! Security policy and error types for ADR-033 mandatory manifest signatures.
//!
//! Defines the `SecurityPolicy` mount policy (default: Strict) and
//! structured `SecurityError` diagnostics for deterministic failure reasons.
/// Manifest signature verification policy.
///
/// Controls how the runtime handles unsigned or invalid signatures
/// when opening an RVF file. Default is `Strict` — no signature means
/// no mount in production.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum SecurityPolicy {
/// No signature verification. For development and testing only.
Permissive = 0x00,
/// Warn on missing or invalid signatures, but allow open.
WarnOnly = 0x01,
/// Require valid signature on Level 0 manifest.
/// DEFAULT for production.
Strict = 0x02,
/// Require valid signatures on Level 0, Level 1, and all
/// hotset-referenced segments. Full chain verification.
Paranoid = 0x03,
}
impl Default for SecurityPolicy {
fn default() -> Self {
Self::Strict
}
}
impl SecurityPolicy {
/// Returns true if signature verification is required at mount time.
pub const fn requires_signature(&self) -> bool {
matches!(*self, Self::Strict | Self::Paranoid)
}
/// Returns true if content hash verification is performed on hotset access.
pub const fn verifies_content_hashes(&self) -> bool {
matches!(*self, Self::WarnOnly | Self::Strict | Self::Paranoid)
}
/// Returns true if Level 1 manifest is also signature-verified.
pub const fn verifies_level1(&self) -> bool {
matches!(*self, Self::Paranoid)
}
}
/// Structured security error with deterministic, stable error codes.
///
/// Every variant includes enough context for logging and diagnostics
/// without exposing internal state that could aid an attacker.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SecurityError {
/// Level 0 manifest has no signature (sig_algo = 0).
UnsignedManifest {
/// Byte offset of the rejected manifest.
manifest_offset: u64,
},
/// Signature is present but cryptographically invalid.
InvalidSignature {
/// Byte offset of the rejected manifest.
manifest_offset: u64,
/// Phase where rejection occurred.
rejection_phase: &'static str,
},
/// Signature is valid but from an unknown/untrusted signer.
UnknownSigner {
/// Byte offset of the rejected manifest.
manifest_offset: u64,
/// Fingerprint of the actual signer (first 16 bytes of public key hash).
actual_signer: [u8; 16],
/// Fingerprint of the expected signer from trust store (if known).
expected_signer: Option<[u8; 16]>,
},
/// Content hash of a hotset-referenced segment does not match.
ContentHashMismatch {
/// Name of the pointer that failed (e.g., "centroid_seg_offset").
pointer_name: &'static str,
/// Content hash stored in Level 0.
expected_hash: [u8; 16],
/// Actual hash of the segment at the pointed offset.
actual_hash: [u8; 16],
/// Byte offset that was followed.
seg_offset: u64,
},
/// Centroid epoch drift exceeds maximum allowed.
EpochDriftExceeded {
/// Current epoch drift value.
epoch_drift: u32,
/// Maximum allowed drift.
max_epoch_drift: u32,
},
/// Level 1 manifest signature invalid (Paranoid mode only).
Level1InvalidSignature {
/// Byte offset of the Level 1 manifest.
manifest_offset: u64,
},
}
impl core::fmt::Display for SecurityError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnsignedManifest { manifest_offset } => {
write!(f, "unsigned manifest at offset 0x{manifest_offset:X}")
}
Self::InvalidSignature {
manifest_offset,
rejection_phase,
} => {
write!(
f,
"invalid signature at offset 0x{manifest_offset:X} \
(phase: {rejection_phase})"
)
}
Self::UnknownSigner {
manifest_offset, ..
} => {
write!(f, "unknown signer at offset 0x{manifest_offset:X}")
}
Self::ContentHashMismatch {
pointer_name,
seg_offset,
..
} => {
write!(
f,
"content hash mismatch for {pointer_name} \
at offset 0x{seg_offset:X}"
)
}
Self::EpochDriftExceeded {
epoch_drift,
max_epoch_drift,
} => {
write!(
f,
"centroid epoch drift {epoch_drift} exceeds max {max_epoch_drift}"
)
}
Self::Level1InvalidSignature { manifest_offset } => {
write!(
f,
"Level 1 manifest invalid signature at offset 0x{manifest_offset:X}"
)
}
}
}
}
/// Content hash fields stored in the Level 0 reserved area (ADR-033 §1).
///
/// 96 bytes total: 5 content hashes (16 bytes each) + centroid_epoch (4) +
/// max_epoch_drift (4) + reserved (8).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct HardeningFields {
/// SHAKE-256 truncated to 128 bits of the entrypoint segment payload.
pub entrypoint_content_hash: [u8; 16],
/// SHAKE-256 truncated to 128 bits of the toplayer segment payload.
pub toplayer_content_hash: [u8; 16],
/// SHAKE-256 truncated to 128 bits of the centroid segment payload.
pub centroid_content_hash: [u8; 16],
/// SHAKE-256 truncated to 128 bits of the quantdict segment payload.
pub quantdict_content_hash: [u8; 16],
/// SHAKE-256 truncated to 128 bits of the hot_cache segment payload.
pub hot_cache_content_hash: [u8; 16],
/// Monotonic counter incremented on centroid recomputation.
pub centroid_epoch: u32,
/// Maximum allowed drift before forced recompute.
pub max_epoch_drift: u32,
/// Reserved for future hardening fields.
pub reserved: [u8; 8],
}
const _: () = assert!(core::mem::size_of::<HardeningFields>() == 96);
impl HardeningFields {
/// Offset within the Level 0 reserved area (0xF00 + 109 = 0xF6D).
/// Starts after FileIdentity (68 bytes), COW pointers (24 bytes),
/// and double-root mechanism (17 bytes).
pub const RESERVED_OFFSET: usize = 109;
/// Create zeroed hardening fields.
pub const fn zeroed() -> Self {
Self {
entrypoint_content_hash: [0u8; 16],
toplayer_content_hash: [0u8; 16],
centroid_content_hash: [0u8; 16],
quantdict_content_hash: [0u8; 16],
hot_cache_content_hash: [0u8; 16],
centroid_epoch: 0,
max_epoch_drift: 64,
reserved: [0u8; 8],
}
}
/// Serialize to 96 bytes (little-endian).
pub fn to_bytes(&self) -> [u8; 96] {
let mut buf = [0u8; 96];
buf[0..16].copy_from_slice(&self.entrypoint_content_hash);
buf[16..32].copy_from_slice(&self.toplayer_content_hash);
buf[32..48].copy_from_slice(&self.centroid_content_hash);
buf[48..64].copy_from_slice(&self.quantdict_content_hash);
buf[64..80].copy_from_slice(&self.hot_cache_content_hash);
buf[80..84].copy_from_slice(&self.centroid_epoch.to_le_bytes());
buf[84..88].copy_from_slice(&self.max_epoch_drift.to_le_bytes());
buf[88..96].copy_from_slice(&self.reserved);
buf
}
/// Deserialize from 96 bytes (little-endian).
pub fn from_bytes(buf: &[u8; 96]) -> Self {
let mut entrypoint_content_hash = [0u8; 16];
let mut toplayer_content_hash = [0u8; 16];
let mut centroid_content_hash = [0u8; 16];
let mut quantdict_content_hash = [0u8; 16];
let mut hot_cache_content_hash = [0u8; 16];
let mut reserved = [0u8; 8];
entrypoint_content_hash.copy_from_slice(&buf[0..16]);
toplayer_content_hash.copy_from_slice(&buf[16..32]);
centroid_content_hash.copy_from_slice(&buf[32..48]);
quantdict_content_hash.copy_from_slice(&buf[48..64]);
hot_cache_content_hash.copy_from_slice(&buf[64..80]);
let centroid_epoch = u32::from_le_bytes([buf[80], buf[81], buf[82], buf[83]]);
let max_epoch_drift = u32::from_le_bytes([buf[84], buf[85], buf[86], buf[87]]);
reserved.copy_from_slice(&buf[88..96]);
Self {
entrypoint_content_hash,
toplayer_content_hash,
centroid_content_hash,
quantdict_content_hash,
hot_cache_content_hash,
centroid_epoch,
max_epoch_drift,
reserved,
}
}
/// Check if all content hashes are zero (no hardening data stored).
pub fn is_empty(&self) -> bool {
self.entrypoint_content_hash == [0u8; 16]
&& self.toplayer_content_hash == [0u8; 16]
&& self.centroid_content_hash == [0u8; 16]
&& self.quantdict_content_hash == [0u8; 16]
&& self.hot_cache_content_hash == [0u8; 16]
&& self.centroid_epoch == 0
}
/// Get the content hash for a named pointer.
pub fn hash_for_pointer(&self, pointer_name: &str) -> Option<&[u8; 16]> {
match pointer_name {
"entrypoint" => Some(&self.entrypoint_content_hash),
"toplayer" => Some(&self.toplayer_content_hash),
"centroid" => Some(&self.centroid_content_hash),
"quantdict" => Some(&self.quantdict_content_hash),
"hot_cache" => Some(&self.hot_cache_content_hash),
_ => None,
}
}
/// Compute epoch drift relative to the manifest's global epoch.
pub fn epoch_drift(&self, manifest_epoch: u32) -> u32 {
manifest_epoch.saturating_sub(self.centroid_epoch)
}
/// Check if epoch drift exceeds the maximum allowed.
pub fn is_epoch_drift_exceeded(&self, manifest_epoch: u32) -> bool {
self.epoch_drift(manifest_epoch) > self.max_epoch_drift
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn security_policy_default_is_strict() {
assert_eq!(SecurityPolicy::default(), SecurityPolicy::Strict);
}
#[test]
fn security_policy_signature_required() {
assert!(!SecurityPolicy::Permissive.requires_signature());
assert!(!SecurityPolicy::WarnOnly.requires_signature());
assert!(SecurityPolicy::Strict.requires_signature());
assert!(SecurityPolicy::Paranoid.requires_signature());
}
#[test]
fn security_policy_content_hashes() {
assert!(!SecurityPolicy::Permissive.verifies_content_hashes());
assert!(SecurityPolicy::WarnOnly.verifies_content_hashes());
assert!(SecurityPolicy::Strict.verifies_content_hashes());
assert!(SecurityPolicy::Paranoid.verifies_content_hashes());
}
#[test]
fn security_policy_level1() {
assert!(!SecurityPolicy::Strict.verifies_level1());
assert!(SecurityPolicy::Paranoid.verifies_level1());
}
#[test]
fn security_policy_repr() {
assert_eq!(SecurityPolicy::Permissive as u8, 0x00);
assert_eq!(SecurityPolicy::WarnOnly as u8, 0x01);
assert_eq!(SecurityPolicy::Strict as u8, 0x02);
assert_eq!(SecurityPolicy::Paranoid as u8, 0x03);
}
#[test]
fn hardening_fields_size() {
assert_eq!(core::mem::size_of::<HardeningFields>(), 96);
}
#[test]
fn hardening_fields_round_trip() {
let fields = HardeningFields {
entrypoint_content_hash: [1u8; 16],
toplayer_content_hash: [2u8; 16],
centroid_content_hash: [3u8; 16],
quantdict_content_hash: [4u8; 16],
hot_cache_content_hash: [5u8; 16],
centroid_epoch: 42,
max_epoch_drift: 64,
reserved: [0u8; 8],
};
let bytes = fields.to_bytes();
let decoded = HardeningFields::from_bytes(&bytes);
assert_eq!(fields, decoded);
}
#[test]
fn hardening_fields_zeroed() {
let fields = HardeningFields::zeroed();
assert!(fields.is_empty());
assert_eq!(fields.max_epoch_drift, 64);
}
#[test]
fn hardening_fields_hash_for_pointer() {
let mut fields = HardeningFields::zeroed();
fields.centroid_content_hash = [0xAB; 16];
assert_eq!(fields.hash_for_pointer("centroid"), Some(&[0xAB; 16]));
assert_eq!(fields.hash_for_pointer("unknown"), None);
}
#[test]
fn hardening_fields_epoch_drift() {
let fields = HardeningFields {
centroid_epoch: 10,
max_epoch_drift: 64,
..HardeningFields::zeroed()
};
assert_eq!(fields.epoch_drift(50), 40);
assert!(!fields.is_epoch_drift_exceeded(50));
assert!(fields.is_epoch_drift_exceeded(100));
}
#[test]
fn security_error_display() {
let err = SecurityError::UnsignedManifest {
manifest_offset: 0x1000,
};
let s = alloc::format!("{err}");
assert!(s.contains("unsigned manifest"));
let err = SecurityError::ContentHashMismatch {
pointer_name: "centroid",
expected_hash: [0xAA; 16],
actual_hash: [0xBB; 16],
seg_offset: 0x2000,
};
let s = alloc::format!("{err}");
assert!(s.contains("centroid"));
assert!(s.contains("2000"));
}
#[test]
fn security_error_unknown_signer() {
let err = SecurityError::UnknownSigner {
manifest_offset: 0x3000,
actual_signer: [0x11; 16],
expected_signer: Some([0x22; 16]),
};
let s = alloc::format!("{err}");
assert!(s.contains("unknown signer"));
}
#[test]
fn reserved_offset_fits() {
// 109 + 96 = 205 <= 252 (reserved area size)
assert!(HardeningFields::RESERVED_OFFSET + 96 <= 252);
}
}

View File

@@ -0,0 +1,132 @@
//! 64-byte segment header for the RVF format.
/// The fixed 64-byte header that precedes every segment payload.
///
/// Layout matches the wire format exactly (repr(C), little-endian fields).
/// Aligned to 64 bytes to match SIMD register width and cache-line size.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct SegmentHeader {
/// Magic number: must be `0x52564653` ("RVFS").
pub magic: u32,
/// Segment format version (currently 1).
pub version: u8,
/// Segment type discriminator (see `SegmentType`).
pub seg_type: u8,
/// Bitfield flags (see `SegmentFlags`).
pub flags: u16,
/// Monotonically increasing segment ordinal.
pub segment_id: u64,
/// Byte length of payload (after header, before optional footer).
pub payload_length: u64,
/// Nanosecond UNIX timestamp of segment creation.
pub timestamp_ns: u64,
/// Hash algorithm enum: 0=CRC32C, 1=XXH3-128, 2=SHAKE-256.
pub checksum_algo: u8,
/// Compression enum: 0=none, 1=LZ4, 2=ZSTD, 3=custom.
pub compression: u8,
/// Reserved (must be zero).
pub reserved_0: u16,
/// Reserved (must be zero).
pub reserved_1: u32,
/// First 128 bits of payload hash (algorithm per `checksum_algo`).
pub content_hash: [u8; 16],
/// Original payload size before compression (0 if uncompressed).
pub uncompressed_len: u32,
/// Padding to reach the 64-byte boundary.
pub alignment_pad: u32,
}
// Compile-time assertion: SegmentHeader must be exactly 64 bytes.
const _: () = assert!(core::mem::size_of::<SegmentHeader>() == 64);
impl SegmentHeader {
/// Create a new segment header with the given type and segment ID.
/// All other fields are set to defaults.
pub const fn new(seg_type: u8, segment_id: u64) -> Self {
Self {
magic: crate::constants::SEGMENT_MAGIC,
version: crate::constants::SEGMENT_VERSION,
seg_type,
flags: 0,
segment_id,
payload_length: 0,
timestamp_ns: 0,
checksum_algo: 0,
compression: 0,
reserved_0: 0,
reserved_1: 0,
content_hash: [0u8; 16],
uncompressed_len: 0,
alignment_pad: 0,
}
}
/// Check whether the magic field matches the expected value.
#[inline]
pub const fn is_valid_magic(&self) -> bool {
self.magic == crate::constants::SEGMENT_MAGIC
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::SEGMENT_MAGIC;
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<SegmentHeader>(), 64);
}
#[test]
fn header_alignment() {
assert!(core::mem::align_of::<SegmentHeader>() <= 64);
}
#[test]
fn new_header_has_valid_magic() {
let h = SegmentHeader::new(0x01, 42);
assert!(h.is_valid_magic());
assert_eq!(h.magic, SEGMENT_MAGIC);
assert_eq!(h.seg_type, 0x01);
assert_eq!(h.segment_id, 42);
}
#[test]
fn field_offsets() {
// Verify field offsets match the wire format spec
let h = SegmentHeader::new(0x01, 0);
let base = &h as *const _ as usize;
let magic_off = &h.magic as *const _ as usize - base;
let version_off = &h.version as *const _ as usize - base;
let seg_type_off = &h.seg_type as *const _ as usize - base;
let flags_off = &h.flags as *const _ as usize - base;
let segment_id_off = &h.segment_id as *const _ as usize - base;
let payload_length_off = &h.payload_length as *const _ as usize - base;
let timestamp_ns_off = &h.timestamp_ns as *const _ as usize - base;
let checksum_algo_off = &h.checksum_algo as *const _ as usize - base;
let compression_off = &h.compression as *const _ as usize - base;
let reserved_0_off = &h.reserved_0 as *const _ as usize - base;
let reserved_1_off = &h.reserved_1 as *const _ as usize - base;
let content_hash_off = &h.content_hash as *const _ as usize - base;
let uncompressed_len_off = &h.uncompressed_len as *const _ as usize - base;
let alignment_pad_off = &h.alignment_pad as *const _ as usize - base;
assert_eq!(magic_off, 0x00);
assert_eq!(version_off, 0x04);
assert_eq!(seg_type_off, 0x05);
assert_eq!(flags_off, 0x06);
assert_eq!(segment_id_off, 0x08);
assert_eq!(payload_length_off, 0x10);
assert_eq!(timestamp_ns_off, 0x18);
assert_eq!(checksum_algo_off, 0x20);
assert_eq!(compression_off, 0x21);
assert_eq!(reserved_0_off, 0x22);
assert_eq!(reserved_1_off, 0x24);
assert_eq!(content_hash_off, 0x28);
assert_eq!(uncompressed_len_off, 0x38);
assert_eq!(alignment_pad_off, 0x3C);
}
}

View File

@@ -0,0 +1,197 @@
//! Segment type discriminator for the RVF format.
/// Identifies the kind of data stored in a segment.
///
/// Values `0x00` and `0xF0..=0xFF` are reserved.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum SegmentType {
/// Not a valid segment (uninitialized / zeroed region).
Invalid = 0x00,
/// Raw vector payloads (the actual embeddings).
Vec = 0x01,
/// HNSW adjacency lists, entry points, routing tables.
Index = 0x02,
/// Graph overlay deltas, partition updates, min-cut witnesses.
Overlay = 0x03,
/// Metadata mutations (label changes, deletions, moves).
Journal = 0x04,
/// Segment directory, hotset pointers, epoch state.
Manifest = 0x05,
/// Quantization dictionaries and codebooks.
Quant = 0x06,
/// Arbitrary key-value metadata (tags, provenance, lineage).
Meta = 0x07,
/// Temperature-promoted hot data (vectors + neighbors).
Hot = 0x08,
/// Access counter sketches for temperature decisions.
Sketch = 0x09,
/// Capability manifests, proof of computation, audit trails.
Witness = 0x0A,
/// Domain profile declarations (RVDNA, RVText, etc.).
Profile = 0x0B,
/// Key material, signature chains, certificate anchors.
Crypto = 0x0C,
/// Metadata inverted indexes for filtered search.
MetaIdx = 0x0D,
/// Embedded kernel / unikernel image for self-booting.
Kernel = 0x0E,
/// Embedded eBPF program for kernel fast path.
Ebpf = 0x0F,
/// Embedded WASM bytecode for self-bootstrapping execution.
///
/// A WASM_SEG contains either a WASM microkernel (the RVF query engine
/// compiled to wasm32) or a minimal WASM interpreter that can execute
/// the microkernel. When both are present the file becomes fully
/// self-bootstrapping: any host with raw execution capability can run
/// the embedded interpreter, which in turn runs the microkernel, which
/// processes the RVF data segments.
Wasm = 0x10,
/// Embedded web dashboard bundle (HTML/JS/CSS assets).
///
/// A DASHBOARD_SEG contains a pre-built web application (e.g. Vite +
/// Three.js) that can be served by the RVF HTTP server at `/`. The
/// payload is a 64-byte `DashboardHeader` followed by a file table
/// and concatenated file contents.
Dashboard = 0x11,
/// COW cluster mapping.
CowMap = 0x20,
/// Cluster reference counts.
Refcount = 0x21,
/// Vector membership filter.
Membership = 0x22,
/// Sparse delta patches.
Delta = 0x23,
/// Serialized transfer prior (cross-domain posterior summaries + cost EMAs).
TransferPrior = 0x30,
/// Policy kernel configuration and performance history.
PolicyKernel = 0x31,
/// Cost curve convergence data for acceleration tracking.
CostCurve = 0x32,
/// Federated learning export manifest: contributor pseudonym, export timestamp,
/// included segment IDs, privacy budget spent, format version.
FederatedManifest = 0x33,
/// Differential privacy attestation: epsilon/delta values, noise mechanism,
/// sensitivity bounds, clipping parameters.
DiffPrivacyProof = 0x34,
/// PII stripping attestation: redacted fields, rules fired,
/// hash of pre-redaction content.
RedactionLog = 0x35,
/// Federated-averaged SONA weights: aggregated LoRA deltas,
/// participation count, round number, convergence metrics.
AggregateWeights = 0x36,
}
impl TryFrom<u8> for SegmentType {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Invalid),
0x01 => Ok(Self::Vec),
0x02 => Ok(Self::Index),
0x03 => Ok(Self::Overlay),
0x04 => Ok(Self::Journal),
0x05 => Ok(Self::Manifest),
0x06 => Ok(Self::Quant),
0x07 => Ok(Self::Meta),
0x08 => Ok(Self::Hot),
0x09 => Ok(Self::Sketch),
0x0A => Ok(Self::Witness),
0x0B => Ok(Self::Profile),
0x0C => Ok(Self::Crypto),
0x0D => Ok(Self::MetaIdx),
0x0E => Ok(Self::Kernel),
0x0F => Ok(Self::Ebpf),
0x10 => Ok(Self::Wasm),
0x11 => Ok(Self::Dashboard),
0x20 => Ok(Self::CowMap),
0x21 => Ok(Self::Refcount),
0x22 => Ok(Self::Membership),
0x23 => Ok(Self::Delta),
0x30 => Ok(Self::TransferPrior),
0x31 => Ok(Self::PolicyKernel),
0x32 => Ok(Self::CostCurve),
0x33 => Ok(Self::FederatedManifest),
0x34 => Ok(Self::DiffPrivacyProof),
0x35 => Ok(Self::RedactionLog),
0x36 => Ok(Self::AggregateWeights),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_all_variants() {
let variants = [
SegmentType::Invalid,
SegmentType::Vec,
SegmentType::Index,
SegmentType::Overlay,
SegmentType::Journal,
SegmentType::Manifest,
SegmentType::Quant,
SegmentType::Meta,
SegmentType::Hot,
SegmentType::Sketch,
SegmentType::Witness,
SegmentType::Profile,
SegmentType::Crypto,
SegmentType::MetaIdx,
SegmentType::Kernel,
SegmentType::Ebpf,
SegmentType::Wasm,
SegmentType::Dashboard,
SegmentType::CowMap,
SegmentType::Refcount,
SegmentType::Membership,
SegmentType::Delta,
SegmentType::TransferPrior,
SegmentType::PolicyKernel,
SegmentType::CostCurve,
SegmentType::FederatedManifest,
SegmentType::DiffPrivacyProof,
SegmentType::RedactionLog,
SegmentType::AggregateWeights,
];
for v in variants {
let raw = v as u8;
assert_eq!(SegmentType::try_from(raw), Ok(v));
}
}
#[test]
fn invalid_value_returns_err() {
assert_eq!(SegmentType::try_from(0x12), Err(0x12));
assert_eq!(SegmentType::try_from(0x37), Err(0x37));
assert_eq!(SegmentType::try_from(0xF0), Err(0xF0));
assert_eq!(SegmentType::try_from(0xFF), Err(0xFF));
}
#[test]
fn domain_expansion_discriminants() {
assert_eq!(SegmentType::TransferPrior as u8, 0x30);
assert_eq!(SegmentType::PolicyKernel as u8, 0x31);
assert_eq!(SegmentType::CostCurve as u8, 0x32);
}
#[test]
fn federation_discriminants() {
assert_eq!(SegmentType::FederatedManifest as u8, 0x33);
assert_eq!(SegmentType::DiffPrivacyProof as u8, 0x34);
assert_eq!(SegmentType::RedactionLog as u8, 0x35);
assert_eq!(SegmentType::AggregateWeights as u8, 0x36);
}
#[test]
fn kernel_ebpf_wasm_discriminants() {
assert_eq!(SegmentType::Kernel as u8, 0x0E);
assert_eq!(SegmentType::Ebpf as u8, 0x0F);
assert_eq!(SegmentType::Wasm as u8, 0x10);
}
}

View File

@@ -0,0 +1,341 @@
//! Pure no_std SHA-256 (FIPS 180-4) and HMAC-SHA256 (RFC 2104).
//!
//! Zero external dependencies. Verified against NIST test vectors.
/// SHA-256 digest size in bytes.
pub const DIGEST_SIZE: usize = 32;
/// SHA-256 block size in bytes.
pub const BLOCK_SIZE: usize = 64;
/// Round constants: first 32 bits of fractional parts of cube roots of first 64 primes.
const K: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];
/// Initial hash values: first 32 bits of fractional parts of square roots of first 8 primes.
const H_INIT: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
];
/// Streaming SHA-256 hasher.
pub struct Sha256 {
state: [u32; 8],
buffer: [u8; 64],
buffer_len: usize,
total_len: u64,
}
impl Sha256 {
/// Create a new SHA-256 hasher.
pub fn new() -> Self {
Self {
state: H_INIT,
buffer: [0u8; 64],
buffer_len: 0,
total_len: 0,
}
}
/// Feed data into the hasher.
pub fn update(&mut self, data: &[u8]) {
self.total_len += data.len() as u64;
let mut offset = 0;
// Fill partial buffer.
if self.buffer_len > 0 {
let need = 64 - self.buffer_len;
let take = if need < data.len() { need } else { data.len() };
self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&data[..take]);
self.buffer_len += take;
offset = take;
if self.buffer_len == 64 {
let block = self.buffer;
self.compress(&block);
self.buffer_len = 0;
}
}
// Process full blocks directly.
while offset + 64 <= data.len() {
let mut block = [0u8; 64];
block.copy_from_slice(&data[offset..offset + 64]);
self.compress(&block);
offset += 64;
}
// Buffer remaining.
let remaining = data.len() - offset;
if remaining > 0 {
self.buffer[..remaining].copy_from_slice(&data[offset..]);
self.buffer_len = remaining;
}
}
/// Finalize and return the 32-byte digest.
pub fn finalize(mut self) -> [u8; 32] {
let bit_len = self.total_len * 8;
// Append 0x80 padding byte.
self.buffer[self.buffer_len] = 0x80;
self.buffer_len += 1;
// If no room for 8-byte length, process block and start new one.
if self.buffer_len > 56 {
while self.buffer_len < 64 {
self.buffer[self.buffer_len] = 0;
self.buffer_len += 1;
}
let block = self.buffer;
self.compress(&block);
self.buffer = [0u8; 64];
self.buffer_len = 0;
}
// Zero-fill up to byte 56.
while self.buffer_len < 56 {
self.buffer[self.buffer_len] = 0;
self.buffer_len += 1;
}
// Append bit length as big-endian u64.
self.buffer[56..64].copy_from_slice(&bit_len.to_be_bytes());
let block = self.buffer;
self.compress(&block);
// Produce output.
let mut out = [0u8; 32];
for i in 0..8 {
out[i * 4..(i + 1) * 4].copy_from_slice(&self.state[i].to_be_bytes());
}
out
}
/// Process a single 64-byte block.
fn compress(&mut self, block: &[u8; 64]) {
let mut w = [0u32; 64];
// First 16 words from the block (big-endian).
for i in 0..16 {
w[i] = u32::from_be_bytes([
block[i * 4],
block[i * 4 + 1],
block[i * 4 + 2],
block[i * 4 + 3],
]);
}
// Extend to 64 words.
for i in 16..64 {
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16]
.wrapping_add(s0)
.wrapping_add(w[i - 7])
.wrapping_add(s1);
}
// Initialize working variables.
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = self.state;
// 64 rounds.
for i in 0..64 {
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let ch = (e & f) ^ ((!e) & g);
let temp1 = h
.wrapping_add(s1)
.wrapping_add(ch)
.wrapping_add(K[i])
.wrapping_add(w[i]);
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
let maj = (a & b) ^ (a & c) ^ (b & c);
let temp2 = s0.wrapping_add(maj);
h = g;
g = f;
f = e;
e = d.wrapping_add(temp1);
d = c;
c = b;
b = a;
a = temp1.wrapping_add(temp2);
}
// Update state.
self.state[0] = self.state[0].wrapping_add(a);
self.state[1] = self.state[1].wrapping_add(b);
self.state[2] = self.state[2].wrapping_add(c);
self.state[3] = self.state[3].wrapping_add(d);
self.state[4] = self.state[4].wrapping_add(e);
self.state[5] = self.state[5].wrapping_add(f);
self.state[6] = self.state[6].wrapping_add(g);
self.state[7] = self.state[7].wrapping_add(h);
}
}
/// One-shot SHA-256 hash.
pub fn sha256(data: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(data);
h.finalize()
}
/// HMAC-SHA256 (RFC 2104).
pub fn hmac_sha256(key: &[u8], message: &[u8]) -> [u8; 32] {
// If key > block size, hash it.
let key_hash: [u8; 32];
let k: &[u8] = if key.len() > BLOCK_SIZE {
key_hash = sha256(key);
&key_hash
} else {
key
};
// Pad key to block size.
let mut k_pad = [0u8; BLOCK_SIZE];
k_pad[..k.len()].copy_from_slice(k);
// Inner hash: H((K ⊕ ipad) || message)
let mut inner_key = [0u8; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
inner_key[i] = k_pad[i] ^ 0x36;
}
let mut hasher = Sha256::new();
hasher.update(&inner_key);
hasher.update(message);
let inner_hash = hasher.finalize();
// Outer hash: H((K ⊕ opad) || inner_hash)
let mut outer_key = [0u8; BLOCK_SIZE];
for i in 0..BLOCK_SIZE {
outer_key[i] = k_pad[i] ^ 0x5c;
}
let mut hasher = Sha256::new();
hasher.update(&outer_key);
hasher.update(&inner_hash);
hasher.finalize()
}
/// Constant-time comparison of two 32-byte digests.
pub fn ct_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
let mut diff = 0u8;
for i in 0..32 {
diff |= a[i] ^ b[i];
}
diff == 0
}
#[cfg(test)]
mod tests {
use super::*;
/// Parse a hex string to a 32-byte array.
fn hex32(hex: &str) -> [u8; 32] {
let mut out = [0u8; 32];
for i in 0..32 {
out[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap();
}
out
}
// --- NIST SHA-256 Test Vectors (FIPS 180-4 examples) ---
#[test]
fn sha256_empty() {
let expected = hex32("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
assert_eq!(sha256(b""), expected);
}
#[test]
fn sha256_abc() {
let expected = hex32("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
assert_eq!(sha256(b"abc"), expected);
}
#[test]
fn sha256_two_block() {
// "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" — 56 bytes, spans two blocks.
let expected = hex32("248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
assert_eq!(
sha256(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
expected
);
}
#[test]
fn sha256_streaming_matches_oneshot() {
let data = b"The quick brown fox jumps over the lazy dog";
let expected = sha256(data);
// Feed in small chunks.
let mut h = Sha256::new();
h.update(&data[..10]);
h.update(&data[10..30]);
h.update(&data[30..]);
assert_eq!(h.finalize(), expected);
}
#[test]
fn sha256_exactly_64_bytes() {
let data = [0x42u8; 64]; // Exactly one block.
let result = sha256(&data);
// Just verify it produces a valid digest (no panic).
assert_ne!(result, [0u8; 32]);
}
#[test]
fn sha256_128_bytes() {
let data = [0xAB; 128]; // Exactly two blocks.
let result = sha256(&data);
assert_ne!(result, [0u8; 32]);
}
// --- HMAC-SHA256 Test Vector (RFC 4231 Test Case 2) ---
#[test]
fn hmac_sha256_rfc4231_case2() {
// Key = "Jefe", Data = "what do ya want for nothing?"
let key = b"Jefe";
let data = b"what do ya want for nothing?";
let expected = hex32("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
assert_eq!(hmac_sha256(key, data), expected);
}
#[test]
fn hmac_sha256_rfc4231_case1() {
// Key = 20 bytes of 0x0b, Data = "Hi There"
let key = [0x0bu8; 20];
let data = b"Hi There";
let expected = hex32("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
assert_eq!(hmac_sha256(&key, data), expected);
}
#[test]
fn hmac_sha256_long_key() {
// Key longer than block size (131 bytes of 0xaa).
let key = [0xAAu8; 131];
let data = b"Test Using Larger Than Block-Size Key - Hash Key First";
let expected = hex32("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");
assert_eq!(hmac_sha256(&key, data), expected);
}
#[test]
fn ct_eq_same() {
let a = sha256(b"test");
assert!(ct_eq(&a, &a));
}
#[test]
fn ct_eq_different() {
let a = sha256(b"test1");
let b = sha256(b"test2");
assert!(!ct_eq(&a, &b));
}
}

View File

@@ -0,0 +1,120 @@
//! Signature algorithm identifiers and the signature footer struct.
/// Cryptographic signature algorithm.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
pub enum SignatureAlgo {
/// Ed25519 (64-byte signature, classical).
Ed25519 = 0,
/// ML-DSA-65 (3,309-byte signature, NIST Level 3 post-quantum).
MlDsa65 = 1,
/// SLH-DSA-128s (7,856-byte signature, NIST Level 1 post-quantum).
SlhDsa128s = 2,
}
impl SignatureAlgo {
/// Expected signature byte length for this algorithm.
pub const fn sig_length(self) -> u16 {
match self {
Self::Ed25519 => 64,
Self::MlDsa65 => 3309,
Self::SlhDsa128s => 7856,
}
}
/// Whether this algorithm provides post-quantum security.
pub const fn is_post_quantum(self) -> bool {
match self {
Self::Ed25519 => false,
Self::MlDsa65 | Self::SlhDsa128s => true,
}
}
}
impl TryFrom<u16> for SignatureAlgo {
type Error = u16;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Ed25519),
1 => Ok(Self::MlDsa65),
2 => Ok(Self::SlhDsa128s),
other => Err(other),
}
}
}
/// The signature footer appended after a segment payload when the `SIGNED`
/// flag is set.
///
/// Wire layout (variable-length on the wire, fixed-size in memory):
/// ```text
/// Offset Type Field
/// 0x00 u16 sig_algo
/// 0x02 u16 sig_length
/// 0x04 [u8] signature (sig_length bytes)
/// var u32 footer_length (total footer size for backward scan)
/// ```
///
/// This struct uses a fixed-size buffer large enough for the largest
/// supported algorithm (SLH-DSA-128s = 7,856 bytes).
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SignatureFooter {
/// Signature algorithm.
pub sig_algo: u16,
/// Byte length of the signature.
pub sig_length: u16,
/// Signature bytes (only the first `sig_length` bytes are meaningful).
pub signature: [u8; Self::MAX_SIG_LEN],
/// Total footer size (for backward scanning).
pub footer_length: u32,
}
impl SignatureFooter {
/// Maximum signature length across all supported algorithms.
pub const MAX_SIG_LEN: usize = 7856;
/// Compute the expected footer length from the signature length.
/// Layout: 2 (sig_algo) + 2 (sig_length) + sig_length + 4 (footer_length).
pub const fn compute_footer_length(sig_length: u16) -> u32 {
2 + 2 + sig_length as u32 + 4
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn algo_round_trip() {
for raw in 0..=2u16 {
let a = SignatureAlgo::try_from(raw).unwrap();
assert_eq!(a as u16, raw);
}
assert_eq!(SignatureAlgo::try_from(3), Err(3));
}
#[test]
fn sig_lengths() {
assert_eq!(SignatureAlgo::Ed25519.sig_length(), 64);
assert_eq!(SignatureAlgo::MlDsa65.sig_length(), 3309);
assert_eq!(SignatureAlgo::SlhDsa128s.sig_length(), 7856);
}
#[test]
fn post_quantum_flag() {
assert!(!SignatureAlgo::Ed25519.is_post_quantum());
assert!(SignatureAlgo::MlDsa65.is_post_quantum());
assert!(SignatureAlgo::SlhDsa128s.is_post_quantum());
}
#[test]
fn footer_length_computation() {
// Ed25519: 2 + 2 + 64 + 4 = 72
assert_eq!(SignatureFooter::compute_footer_length(64), 72);
// ML-DSA-65: 2 + 2 + 3309 + 4 = 3317
assert_eq!(SignatureFooter::compute_footer_length(3309), 3317);
}
}

View File

@@ -0,0 +1,402 @@
//! WASM_SEG (0x10) types for self-bootstrapping RVF files.
//!
//! Defines the 64-byte `WasmHeader` and associated enums.
//! A WASM_SEG embeds WASM bytecode that enables an RVF file to carry its
//! own execution runtime. When combined with the data segments (VEC_SEG,
//! INDEX_SEG, etc.), this makes the file fully self-bootstrapping:
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────┐
//! │ .rvf file │
//! │ │
//! │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
//! │ │ WASM_SEG │ │ WASM_SEG │ │ VEC_SEG │ │
//! │ │ role=Interp │ │ role=uKernel │ │ (data) │ │
//! │ │ ~50 KB │ │ ~5.5 KB │ │ │ │
//! │ └──────┬──────┘ └──────┬───────┘ └───────┬───────┘ │
//! │ │ │ │ │
//! │ │ executes │ processes │ │
//! │ └───────────────►└──────────────────►│ │
//! │ │
//! │ Layer 0: Raw bytes │
//! │ Layer 1: Embedded WASM interpreter (native bootstrap) │
//! │ Layer 2: WASM microkernel (query engine) │
//! │ Layer 3: RVF data (vectors, indexes, manifests) │
//! └──────────────────────────────────────────────────────────┘
//! ```
//!
//! The host only needs raw execution capability. RVF becomes
//! self-bootstrapping — "runs anywhere compute exists."
use crate::error::RvfError;
/// Magic number for `WasmHeader`: "RVWM" in big-endian.
pub const WASM_MAGIC: u32 = 0x5256_574D;
/// Role of the embedded WASM module within the bootstrap chain.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum WasmRole {
/// RVF microkernel: the query/ingest engine compiled to WASM.
/// This is the 5.5 KB Cognitum tile runtime with 14+ exports.
Microkernel = 0x00,
/// Minimal WASM interpreter: enables self-bootstrapping on hosts
/// that lack a native WASM runtime. The interpreter runs the
/// microkernel, which then processes RVF data.
Interpreter = 0x01,
/// Combined interpreter + microkernel in a single module.
/// The interpreter is linked with the microkernel for zero-copy
/// bootstrap on bare environments.
Combined = 0x02,
/// Domain-specific extension module (e.g., custom distance
/// functions, codon decoder for RVDNA, token scorer for RVText).
Extension = 0x03,
/// Control plane module: store management, export, segment
/// parsing, and file-level operations.
ControlPlane = 0x04,
}
impl TryFrom<u8> for WasmRole {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Microkernel),
0x01 => Ok(Self::Interpreter),
0x02 => Ok(Self::Combined),
0x03 => Ok(Self::Extension),
0x04 => Ok(Self::ControlPlane),
_ => Err(RvfError::InvalidEnumValue {
type_name: "WasmRole",
value: value as u64,
}),
}
}
}
/// Target platform hint for the WASM module.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum WasmTarget {
/// Generic wasm32 (runs on any compliant runtime).
Wasm32 = 0x00,
/// WASI Preview 1 (requires WASI syscalls).
WasiP1 = 0x01,
/// WASI Preview 2 (component model).
WasiP2 = 0x02,
/// Browser-optimized (expects Web APIs via imports).
Browser = 0x03,
/// Bare-metal tile (no imports beyond host-tile protocol).
BareTile = 0x04,
}
impl TryFrom<u8> for WasmTarget {
type Error = RvfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Wasm32),
0x01 => Ok(Self::WasiP1),
0x02 => Ok(Self::WasiP2),
0x03 => Ok(Self::Browser),
0x04 => Ok(Self::BareTile),
_ => Err(RvfError::InvalidEnumValue {
type_name: "WasmTarget",
value: value as u64,
}),
}
}
}
/// WASM module feature requirements (bitfield).
pub const WASM_FEAT_SIMD: u16 = 1 << 0;
pub const WASM_FEAT_BULK_MEMORY: u16 = 1 << 1;
pub const WASM_FEAT_MULTI_VALUE: u16 = 1 << 2;
pub const WASM_FEAT_REFERENCE_TYPES: u16 = 1 << 3;
pub const WASM_FEAT_THREADS: u16 = 1 << 4;
pub const WASM_FEAT_TAIL_CALL: u16 = 1 << 5;
pub const WASM_FEAT_GC: u16 = 1 << 6;
pub const WASM_FEAT_EXCEPTION_HANDLING: u16 = 1 << 7;
/// 64-byte header for WASM_SEG payloads.
///
/// Follows the standard 64-byte `SegmentHeader`. The WASM bytecode
/// follows immediately after this header within the segment payload.
///
/// For self-bootstrapping files, two WASM_SEGs are present:
/// 1. `role = Interpreter` — a minimal WASM interpreter (~50 KB)
/// 2. `role = Microkernel` — the RVF query engine (~5.5 KB)
///
/// The bootstrap sequence is:
/// 1. Host reads file, finds WASM_SEG with `role = Interpreter`
/// 2. Host loads interpreter bytecode into any available execution engine
/// 3. Interpreter instantiates the microkernel WASM_SEG
/// 4. Microkernel processes VEC_SEG, INDEX_SEG, etc.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct WasmHeader {
/// Magic: `WASM_MAGIC` (0x5256574D, "RVWM").
pub wasm_magic: u32,
/// WasmHeader format version (currently 1).
pub header_version: u16,
/// Role in the bootstrap chain (see `WasmRole`).
pub role: u8,
/// Target platform (see `WasmTarget`).
pub target: u8,
/// Required WASM features bitfield (see `WASM_FEAT_*`).
pub required_features: u16,
/// Number of exports in the WASM module.
pub export_count: u16,
/// Uncompressed WASM bytecode size (bytes).
pub bytecode_size: u32,
/// Compressed bytecode size (0 if uncompressed).
pub compressed_size: u32,
/// Compression algorithm (same enum as SegmentHeader).
pub compression: u8,
/// Minimum linear memory pages required (64 KB each).
pub min_memory_pages: u8,
/// Maximum linear memory pages (0 = no limit).
pub max_memory_pages: u8,
/// Number of WASM tables.
pub table_count: u8,
/// SHAKE-256-256 hash of uncompressed bytecode.
pub bytecode_hash: [u8; 32],
/// Priority order for bootstrap resolution (lower = tried first).
/// The interpreter with lowest priority is used when multiple are present.
pub bootstrap_priority: u8,
/// If role=Interpreter, this is the interpreter type:
/// 0x00 = generic stack machine, 0x01 = wasm3-compatible,
/// 0x02 = wamr-compatible, 0x03 = wasmi-compatible.
pub interpreter_type: u8,
/// Reserved (must be zero).
pub reserved: [u8; 6],
}
// Compile-time assertion: WasmHeader must be exactly 64 bytes.
const _: () = assert!(core::mem::size_of::<WasmHeader>() == 64);
impl WasmHeader {
/// Serialize the header to a 64-byte little-endian array.
pub fn to_bytes(&self) -> [u8; 64] {
let mut buf = [0u8; 64];
buf[0x00..0x04].copy_from_slice(&self.wasm_magic.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&self.header_version.to_le_bytes());
buf[0x06] = self.role;
buf[0x07] = self.target;
buf[0x08..0x0A].copy_from_slice(&self.required_features.to_le_bytes());
buf[0x0A..0x0C].copy_from_slice(&self.export_count.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&self.bytecode_size.to_le_bytes());
buf[0x10..0x14].copy_from_slice(&self.compressed_size.to_le_bytes());
buf[0x14] = self.compression;
buf[0x15] = self.min_memory_pages;
buf[0x16] = self.max_memory_pages;
buf[0x17] = self.table_count;
buf[0x18..0x38].copy_from_slice(&self.bytecode_hash);
buf[0x38] = self.bootstrap_priority;
buf[0x39] = self.interpreter_type;
buf[0x3A..0x40].copy_from_slice(&self.reserved);
buf
}
/// Deserialize a `WasmHeader` from a 64-byte slice.
pub fn from_bytes(data: &[u8; 64]) -> Result<Self, RvfError> {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != WASM_MAGIC {
return Err(RvfError::BadMagic {
expected: WASM_MAGIC,
got: magic,
});
}
Ok(Self {
wasm_magic: magic,
header_version: u16::from_le_bytes([data[0x04], data[0x05]]),
role: data[0x06],
target: data[0x07],
required_features: u16::from_le_bytes([data[0x08], data[0x09]]),
export_count: u16::from_le_bytes([data[0x0A], data[0x0B]]),
bytecode_size: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
compressed_size: u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]),
compression: data[0x14],
min_memory_pages: data[0x15],
max_memory_pages: data[0x16],
table_count: data[0x17],
bytecode_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x18..0x38]);
h
},
bootstrap_priority: data[0x38],
interpreter_type: data[0x39],
reserved: {
let mut r = [0u8; 6];
r.copy_from_slice(&data[0x3A..0x40]);
r
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_header() -> WasmHeader {
WasmHeader {
wasm_magic: WASM_MAGIC,
header_version: 1,
role: WasmRole::Microkernel as u8,
target: WasmTarget::BareTile as u8,
required_features: WASM_FEAT_SIMD | WASM_FEAT_BULK_MEMORY,
export_count: 14,
bytecode_size: 5500,
compressed_size: 0,
compression: 0,
min_memory_pages: 2, // 128 KB
max_memory_pages: 4, // 256 KB
table_count: 0,
bytecode_hash: [0xAB; 32],
bootstrap_priority: 0,
interpreter_type: 0,
reserved: [0; 6],
}
}
#[test]
fn header_size_is_64() {
assert_eq!(core::mem::size_of::<WasmHeader>(), 64);
}
#[test]
fn magic_bytes_match_ascii() {
let bytes_be = WASM_MAGIC.to_be_bytes();
assert_eq!(&bytes_be, b"RVWM");
}
#[test]
fn round_trip_serialization() {
let original = sample_header();
let bytes = original.to_bytes();
let decoded = WasmHeader::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(decoded.wasm_magic, WASM_MAGIC);
assert_eq!(decoded.header_version, 1);
assert_eq!(decoded.role, WasmRole::Microkernel as u8);
assert_eq!(decoded.target, WasmTarget::BareTile as u8);
assert_eq!(
decoded.required_features,
WASM_FEAT_SIMD | WASM_FEAT_BULK_MEMORY
);
assert_eq!(decoded.export_count, 14);
assert_eq!(decoded.bytecode_size, 5500);
assert_eq!(decoded.compressed_size, 0);
assert_eq!(decoded.compression, 0);
assert_eq!(decoded.min_memory_pages, 2);
assert_eq!(decoded.max_memory_pages, 4);
assert_eq!(decoded.table_count, 0);
assert_eq!(decoded.bytecode_hash, [0xAB; 32]);
assert_eq!(decoded.bootstrap_priority, 0);
assert_eq!(decoded.interpreter_type, 0);
assert_eq!(decoded.reserved, [0; 6]);
}
#[test]
fn bad_magic_returns_error() {
let mut bytes = sample_header().to_bytes();
bytes[0] = 0x00;
let err = WasmHeader::from_bytes(&bytes).unwrap_err();
match err {
RvfError::BadMagic { expected, .. } => assert_eq!(expected, WASM_MAGIC),
other => panic!("expected BadMagic, got {other:?}"),
}
}
#[test]
fn interpreter_header() {
let h = WasmHeader {
wasm_magic: WASM_MAGIC,
header_version: 1,
role: WasmRole::Interpreter as u8,
target: WasmTarget::Wasm32 as u8,
required_features: 0,
export_count: 3,
bytecode_size: 51_200, // ~50 KB interpreter
compressed_size: 22_000,
compression: 2, // ZSTD
min_memory_pages: 16, // 1 MB
max_memory_pages: 64, // 4 MB
table_count: 1,
bytecode_hash: [0xCD; 32],
bootstrap_priority: 0, // highest priority
interpreter_type: 0x03, // wasmi-compatible
reserved: [0; 6],
};
let bytes = h.to_bytes();
let decoded = WasmHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.role, WasmRole::Interpreter as u8);
assert_eq!(decoded.bytecode_size, 51_200);
assert_eq!(decoded.interpreter_type, 0x03);
}
#[test]
fn combined_bootstrap_header() {
let h = WasmHeader {
wasm_magic: WASM_MAGIC,
header_version: 1,
role: WasmRole::Combined as u8,
target: WasmTarget::Wasm32 as u8,
required_features: WASM_FEAT_SIMD,
export_count: 17,
bytecode_size: 56_700, // interpreter + microkernel
compressed_size: 0,
compression: 0,
min_memory_pages: 16,
max_memory_pages: 64,
table_count: 1,
bytecode_hash: [0xEF; 32],
bootstrap_priority: 0,
interpreter_type: 0,
reserved: [0; 6],
};
let bytes = h.to_bytes();
let decoded = WasmHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded.role, WasmRole::Combined as u8);
assert_eq!(decoded.export_count, 17);
}
#[test]
fn wasm_role_try_from() {
assert_eq!(WasmRole::try_from(0x00), Ok(WasmRole::Microkernel));
assert_eq!(WasmRole::try_from(0x01), Ok(WasmRole::Interpreter));
assert_eq!(WasmRole::try_from(0x02), Ok(WasmRole::Combined));
assert_eq!(WasmRole::try_from(0x03), Ok(WasmRole::Extension));
assert_eq!(WasmRole::try_from(0x04), Ok(WasmRole::ControlPlane));
assert!(WasmRole::try_from(0x05).is_err());
assert!(WasmRole::try_from(0xFF).is_err());
}
#[test]
fn wasm_target_try_from() {
assert_eq!(WasmTarget::try_from(0x00), Ok(WasmTarget::Wasm32));
assert_eq!(WasmTarget::try_from(0x01), Ok(WasmTarget::WasiP1));
assert_eq!(WasmTarget::try_from(0x02), Ok(WasmTarget::WasiP2));
assert_eq!(WasmTarget::try_from(0x03), Ok(WasmTarget::Browser));
assert_eq!(WasmTarget::try_from(0x04), Ok(WasmTarget::BareTile));
assert!(WasmTarget::try_from(0x05).is_err());
assert!(WasmTarget::try_from(0xFF).is_err());
}
#[test]
fn feature_flags_bit_positions() {
assert_eq!(WASM_FEAT_SIMD, 0x0001);
assert_eq!(WASM_FEAT_BULK_MEMORY, 0x0002);
assert_eq!(WASM_FEAT_MULTI_VALUE, 0x0004);
assert_eq!(WASM_FEAT_REFERENCE_TYPES, 0x0008);
assert_eq!(WASM_FEAT_THREADS, 0x0010);
assert_eq!(WASM_FEAT_TAIL_CALL, 0x0020);
assert_eq!(WASM_FEAT_GC, 0x0040);
assert_eq!(WASM_FEAT_EXCEPTION_HANDLING, 0x0080);
}
}

View File

@@ -0,0 +1,513 @@
//! Witness bundle types for ADR-035 capability reports.
//!
//! A witness bundle is a signed, self-contained evidence record of a task
//! execution. It captures spec, plan, tool trace, diff, test log, cost,
//! latency, and governance mode — everything needed for deterministic replay
//! and audit.
//!
//! Wire format: 64-byte header + TLV sections + optional HMAC-SHA256 signature.
/// Magic bytes for witness bundle: "RVWW" (RuVector Witness).
pub const WITNESS_MAGIC: u32 = 0x5257_5657;
/// Size of the witness header in bytes.
pub const WITNESS_HEADER_SIZE: usize = 64;
// --- Flags ---
/// Witness bundle is signed (HMAC-SHA256).
pub const WIT_SIGNED: u16 = 0x0001;
/// Witness bundle has a spec section.
pub const WIT_HAS_SPEC: u16 = 0x0002;
/// Witness bundle has a plan section.
pub const WIT_HAS_PLAN: u16 = 0x0004;
/// Witness bundle has a tool trace section.
pub const WIT_HAS_TRACE: u16 = 0x0008;
/// Witness bundle has a diff section.
pub const WIT_HAS_DIFF: u16 = 0x0010;
/// Witness bundle has a test log section.
pub const WIT_HAS_TEST_LOG: u16 = 0x0020;
/// Witness bundle has a postmortem section.
pub const WIT_HAS_POSTMORTEM: u16 = 0x0040;
// --- TLV tags ---
/// Tag: task spec / prompt text.
pub const WIT_TAG_SPEC: u16 = 0x0001;
/// Tag: plan graph (text or structured).
pub const WIT_TAG_PLAN: u16 = 0x0002;
/// Tag: tool call trace (array of ToolCallEntry).
pub const WIT_TAG_TRACE: u16 = 0x0003;
/// Tag: code diff (unified diff text).
pub const WIT_TAG_DIFF: u16 = 0x0004;
/// Tag: test output log.
pub const WIT_TAG_TEST_LOG: u16 = 0x0005;
/// Tag: postmortem / failure analysis.
pub const WIT_TAG_POSTMORTEM: u16 = 0x0006;
// --- Enums ---
/// Task execution outcome.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum TaskOutcome {
/// Task completed with passing tests and merged diff.
Solved = 0,
/// Task attempted but tests fail or diff rejected.
Failed = 1,
/// Task skipped (precondition not met).
Skipped = 2,
/// Task errored (infrastructure or tool failure).
Errored = 3,
}
impl TryFrom<u8> for TaskOutcome {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Solved),
1 => Ok(Self::Failed),
2 => Ok(Self::Skipped),
3 => Ok(Self::Errored),
other => Err(other),
}
}
}
/// Governance mode under which the task was executed.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum GovernanceMode {
/// Read-only plus suggestions. No writes.
Restricted = 0,
/// Writes allowed with human confirmation gates.
Approved = 1,
/// Bounded authority with automatic rollback on violation.
Autonomous = 2,
}
impl TryFrom<u8> for GovernanceMode {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Restricted),
1 => Ok(Self::Approved),
2 => Ok(Self::Autonomous),
other => Err(other),
}
}
}
/// Policy check result for a single tool call.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum PolicyCheck {
/// Tool call allowed by policy.
Allowed = 0,
/// Tool call denied by policy.
Denied = 1,
/// Tool call required human confirmation.
Confirmed = 2,
}
impl TryFrom<u8> for PolicyCheck {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Allowed),
1 => Ok(Self::Denied),
2 => Ok(Self::Confirmed),
other => Err(other),
}
}
}
/// Wire-format witness header (exactly 64 bytes, `repr(C)`).
///
/// ```text
/// Offset Type Field
/// 0x00 u32 magic (0x52575657 "RVWW")
/// 0x04 u16 version
/// 0x06 u16 flags
/// 0x08 [u8; 16] task_id (UUID)
/// 0x18 [u8; 8] policy_hash (SHA-256 truncated)
/// 0x20 u64 created_ns (UNIX epoch nanoseconds)
/// 0x28 u8 outcome (TaskOutcome)
/// 0x29 u8 governance_mode (GovernanceMode)
/// 0x2A u16 tool_call_count
/// 0x2C u32 total_cost_microdollars
/// 0x30 u32 total_latency_ms
/// 0x34 u32 total_tokens
/// 0x38 u16 retry_count
/// 0x3A u16 section_count
/// 0x3C u32 total_bundle_size
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct WitnessHeader {
/// Magic bytes: WITNESS_MAGIC.
pub magic: u32,
/// Format version (currently 1).
pub version: u16,
/// Bitfield flags (WIT_SIGNED, WIT_HAS_SPEC, etc.).
pub flags: u16,
/// Unique task identifier (UUID).
pub task_id: [u8; 16],
/// SHA-256 of the policy file, truncated to 8 bytes.
pub policy_hash: [u8; 8],
/// Creation timestamp (nanoseconds since UNIX epoch).
pub created_ns: u64,
/// Task outcome discriminant.
pub outcome: u8,
/// Governance mode discriminant.
pub governance_mode: u8,
/// Number of tool calls recorded.
pub tool_call_count: u16,
/// Total cost in microdollars (1/1,000,000 USD).
pub total_cost_microdollars: u32,
/// Total wall-clock latency in milliseconds.
pub total_latency_ms: u32,
/// Total tokens consumed (prompt + completion).
pub total_tokens: u32,
/// Number of retries across all tool calls.
pub retry_count: u16,
/// Number of TLV sections in the payload.
pub section_count: u16,
/// Total size of the entire witness bundle (header + payload + sig).
pub total_bundle_size: u32,
}
// Compile-time size assertion.
const _: () = assert!(core::mem::size_of::<WitnessHeader>() == 64);
impl WitnessHeader {
/// Check magic bytes.
pub const fn is_valid_magic(&self) -> bool {
self.magic == WITNESS_MAGIC
}
/// Check if the bundle is signed.
pub const fn is_signed(&self) -> bool {
self.flags & WIT_SIGNED != 0
}
/// Serialize header to a 64-byte array.
pub fn to_bytes(&self) -> [u8; WITNESS_HEADER_SIZE] {
let mut buf = [0u8; WITNESS_HEADER_SIZE];
buf[0..4].copy_from_slice(&self.magic.to_le_bytes());
buf[4..6].copy_from_slice(&self.version.to_le_bytes());
buf[6..8].copy_from_slice(&self.flags.to_le_bytes());
buf[8..24].copy_from_slice(&self.task_id);
buf[24..32].copy_from_slice(&self.policy_hash);
buf[32..40].copy_from_slice(&self.created_ns.to_le_bytes());
buf[40] = self.outcome;
buf[41] = self.governance_mode;
buf[42..44].copy_from_slice(&self.tool_call_count.to_le_bytes());
buf[44..48].copy_from_slice(&self.total_cost_microdollars.to_le_bytes());
buf[48..52].copy_from_slice(&self.total_latency_ms.to_le_bytes());
buf[52..56].copy_from_slice(&self.total_tokens.to_le_bytes());
buf[56..58].copy_from_slice(&self.retry_count.to_le_bytes());
buf[58..60].copy_from_slice(&self.section_count.to_le_bytes());
buf[60..64].copy_from_slice(&self.total_bundle_size.to_le_bytes());
buf
}
/// Deserialize header from a byte slice (>= 64 bytes).
pub fn from_bytes(data: &[u8]) -> Result<Self, crate::RvfError> {
if data.len() < WITNESS_HEADER_SIZE {
return Err(crate::RvfError::SizeMismatch {
expected: WITNESS_HEADER_SIZE,
got: data.len(),
});
}
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != WITNESS_MAGIC {
return Err(crate::RvfError::BadMagic {
expected: WITNESS_MAGIC,
got: magic,
});
}
let mut task_id = [0u8; 16];
task_id.copy_from_slice(&data[8..24]);
let mut policy_hash = [0u8; 8];
policy_hash.copy_from_slice(&data[24..32]);
Ok(Self {
magic,
version: u16::from_le_bytes([data[4], data[5]]),
flags: u16::from_le_bytes([data[6], data[7]]),
task_id,
policy_hash,
created_ns: u64::from_le_bytes([
data[32], data[33], data[34], data[35], data[36], data[37], data[38], data[39],
]),
outcome: data[40],
governance_mode: data[41],
tool_call_count: u16::from_le_bytes([data[42], data[43]]),
total_cost_microdollars: u32::from_le_bytes([data[44], data[45], data[46], data[47]]),
total_latency_ms: u32::from_le_bytes([data[48], data[49], data[50], data[51]]),
total_tokens: u32::from_le_bytes([data[52], data[53], data[54], data[55]]),
retry_count: u16::from_le_bytes([data[56], data[57]]),
section_count: u16::from_le_bytes([data[58], data[59]]),
total_bundle_size: u32::from_le_bytes([data[60], data[61], data[62], data[63]]),
})
}
}
/// A single tool call record within a witness trace.
///
/// Requires the `alloc` feature because the variable-length `action` field
/// uses `Vec<u8>`.
///
/// Variable-length: 32 bytes fixed header + action_len bytes.
///
/// ```text
/// Offset Type Field
/// 0x00 u16 action_len
/// 0x02 u8 policy_check (PolicyCheck)
/// 0x03 u8 _pad
/// 0x04 [u8; 8] args_hash (SHA-256 truncated)
/// 0x0C [u8; 8] result_hash (SHA-256 truncated)
/// 0x14 u32 latency_ms
/// 0x18 u32 cost_microdollars
/// 0x1C u32 tokens
/// 0x20 [u8; action_len] action (UTF-8 tool name)
/// ```
#[cfg(any(feature = "alloc", test))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ToolCallEntry {
/// Tool name / action (e.g. "Bash", "Edit", "Read").
pub action: alloc::vec::Vec<u8>,
/// SHA-256 of args, truncated to 8 bytes.
pub args_hash: [u8; 8],
/// SHA-256 of result, truncated to 8 bytes.
pub result_hash: [u8; 8],
/// Wall-clock latency in milliseconds.
pub latency_ms: u32,
/// Cost in microdollars.
pub cost_microdollars: u32,
/// Tokens consumed.
pub tokens: u32,
/// Policy check result.
pub policy_check: PolicyCheck,
}
/// Fixed header size for a ToolCallEntry (before the action string).
#[cfg(any(feature = "alloc", test))]
pub const TOOL_CALL_FIXED_SIZE: usize = 32;
#[cfg(any(feature = "alloc", test))]
impl ToolCallEntry {
/// Total serialized size.
pub fn wire_size(&self) -> usize {
TOOL_CALL_FIXED_SIZE + self.action.len()
}
/// Serialize to bytes.
pub fn to_bytes(&self) -> alloc::vec::Vec<u8> {
let mut buf = alloc::vec::Vec::with_capacity(self.wire_size());
buf.extend_from_slice(&(self.action.len() as u16).to_le_bytes());
buf.push(self.policy_check as u8);
buf.push(0); // pad
buf.extend_from_slice(&self.args_hash);
buf.extend_from_slice(&self.result_hash);
buf.extend_from_slice(&self.latency_ms.to_le_bytes());
buf.extend_from_slice(&self.cost_microdollars.to_le_bytes());
buf.extend_from_slice(&self.tokens.to_le_bytes());
buf.extend_from_slice(&self.action);
buf
}
/// Deserialize from bytes. Returns (entry, bytes_consumed).
pub fn from_bytes(data: &[u8]) -> Option<(Self, usize)> {
if data.len() < TOOL_CALL_FIXED_SIZE {
return None;
}
let action_len = u16::from_le_bytes([data[0], data[1]]) as usize;
let total = TOOL_CALL_FIXED_SIZE + action_len;
if data.len() < total {
return None;
}
let policy_check = PolicyCheck::try_from(data[2]).ok()?;
let mut args_hash = [0u8; 8];
args_hash.copy_from_slice(&data[4..12]);
let mut result_hash = [0u8; 8];
result_hash.copy_from_slice(&data[12..20]);
let latency_ms = u32::from_le_bytes([data[20], data[21], data[22], data[23]]);
let cost_microdollars = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
let tokens = u32::from_le_bytes([data[28], data[29], data[30], data[31]]);
let action = data[TOOL_CALL_FIXED_SIZE..total].to_vec();
Some((
Self {
action,
args_hash,
result_hash,
latency_ms,
cost_microdollars,
tokens,
policy_check,
},
total,
))
}
}
/// Capability scorecard — aggregate metrics across witness bundles.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Scorecard {
/// Total tasks attempted.
pub total_tasks: u32,
/// Tasks solved with passing tests.
pub solved: u32,
/// Tasks that failed (tests don't pass).
pub failed: u32,
/// Tasks skipped.
pub skipped: u32,
/// Tasks that errored (infra failure).
pub errors: u32,
/// Policy violations detected.
pub policy_violations: u32,
/// Rollbacks performed.
pub rollback_count: u32,
/// Total cost in microdollars.
pub total_cost_microdollars: u64,
/// Median latency in milliseconds.
pub median_latency_ms: u32,
/// 95th percentile latency in milliseconds.
pub p95_latency_ms: u32,
/// Total tokens consumed.
pub total_tokens: u64,
/// Total retries across all tasks.
pub total_retries: u32,
/// Fraction of solved tasks with complete witness bundles.
pub evidence_coverage: f32,
/// Cost per solved task in microdollars.
pub cost_per_solve_microdollars: u32,
/// Solve rate (solved / total_tasks).
pub solve_rate: f32,
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
#[test]
fn witness_header_size() {
assert_eq!(core::mem::size_of::<WitnessHeader>(), 64);
}
#[test]
fn witness_header_round_trip() {
let hdr = WitnessHeader {
magic: WITNESS_MAGIC,
version: 1,
flags: WIT_SIGNED | WIT_HAS_SPEC | WIT_HAS_DIFF,
task_id: [0x42; 16],
policy_hash: [0xAA; 8],
created_ns: 1_700_000_000_000_000_000,
outcome: TaskOutcome::Solved as u8,
governance_mode: GovernanceMode::Approved as u8,
tool_call_count: 12,
total_cost_microdollars: 15_000,
total_latency_ms: 4_500,
total_tokens: 8_000,
retry_count: 2,
section_count: 3,
total_bundle_size: 2048,
};
let bytes = hdr.to_bytes();
assert_eq!(bytes.len(), WITNESS_HEADER_SIZE);
let decoded = WitnessHeader::from_bytes(&bytes).unwrap();
assert_eq!(decoded, hdr);
}
#[test]
fn witness_header_bad_magic() {
let mut bytes = [0u8; 64];
bytes[0..4].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
assert!(WitnessHeader::from_bytes(&bytes).is_err());
}
#[test]
fn witness_header_too_short() {
assert!(WitnessHeader::from_bytes(&[0u8; 32]).is_err());
}
#[test]
fn task_outcome_round_trip() {
for raw in 0..=3u8 {
let o = TaskOutcome::try_from(raw).unwrap();
assert_eq!(o as u8, raw);
}
assert!(TaskOutcome::try_from(4).is_err());
}
#[test]
fn governance_mode_round_trip() {
for raw in 0..=2u8 {
let g = GovernanceMode::try_from(raw).unwrap();
assert_eq!(g as u8, raw);
}
assert!(GovernanceMode::try_from(3).is_err());
}
#[test]
fn policy_check_round_trip() {
for raw in 0..=2u8 {
let p = PolicyCheck::try_from(raw).unwrap();
assert_eq!(p as u8, raw);
}
assert!(PolicyCheck::try_from(3).is_err());
}
#[test]
fn tool_call_entry_round_trip() {
let entry = ToolCallEntry {
action: b"Bash".to_vec(),
args_hash: [0x11; 8],
result_hash: [0x22; 8],
latency_ms: 150,
cost_microdollars: 500,
tokens: 200,
policy_check: PolicyCheck::Allowed,
};
let bytes = entry.to_bytes();
assert_eq!(bytes.len(), TOOL_CALL_FIXED_SIZE + 4);
let (decoded, consumed) = ToolCallEntry::from_bytes(&bytes).unwrap();
assert_eq!(decoded, entry);
assert_eq!(consumed, bytes.len());
}
#[test]
fn tool_call_entry_too_short() {
assert!(ToolCallEntry::from_bytes(&[0u8; 10]).is_none());
}
#[test]
fn witness_flags() {
let flags = WIT_SIGNED | WIT_HAS_SPEC | WIT_HAS_DIFF | WIT_HAS_TEST_LOG;
assert_ne!(flags & WIT_SIGNED, 0);
assert_ne!(flags & WIT_HAS_SPEC, 0);
assert_eq!(flags & WIT_HAS_PLAN, 0);
assert_ne!(flags & WIT_HAS_DIFF, 0);
assert_ne!(flags & WIT_HAS_TEST_LOG, 0);
assert_eq!(flags & WIT_HAS_POSTMORTEM, 0);
}
#[test]
fn scorecard_default_is_zero() {
let s = Scorecard::default();
assert_eq!(s.total_tasks, 0);
assert_eq!(s.solved, 0);
assert_eq!(s.solve_rate, 0.0);
assert_eq!(s.evidence_coverage, 0.0);
}
}