Skip to content

Commit 203979f

Browse files
hyperpolymathclaude
andcommitted
v-ecosystem: receive three more V-tree arrivals for donation
Chains the V-source move pattern onto aerie, volumod, and emergency-button — each has a build-green Zig replacement on the hyperpolymath side. V sources are packaged for donation to the V community. - v-api-interfaces/v-aerie/ (from aerie/src/api/v/; GraphQL+REST CF-NDS gateway) - v-api-interfaces/v-volumod/ (from verification-ecosystem/volumod/src/; audio DSP + Bebop FFI) - v-api-interfaces/v-ambientops-emergency-button/ (from systems-ecosystem/ambientops/emergency-button/src/; panic-safe CLI) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2ab50d6 commit 203979f

40 files changed

Lines changed: 7453 additions & 0 deletions
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= V-lang → Zig Migration Record
4+
:toc:
5+
6+
== Status
7+
8+
*Port complete 2026-04-17; deletion pending build + integration verification.*
9+
10+
The V-lang API gateway (`src/api/v/`) has been fully ported to idiomatic Zig
11+
at `src/api/zig/`. The V sources are retained read-only for reference.
12+
V-lang was banned estate-wide 2026-04-10; see
13+
link:https://github.com/hyperpolymath/standards/blob/main/VLANG-BAN.adoc[VLANG-BAN.adoc].
14+
15+
== V → Zig File Mapping
16+
17+
[cols="2,2,3"]
18+
|===
19+
| V source | Zig replacement | Notes
20+
21+
| `main.v`
22+
| `src/api/zig/main.zig`
23+
| Entry point, HTTP server, gRPC listener, protocol dispatch
24+
25+
| `policy.v`
26+
| `src/api/zig/policy.zig`
27+
| Policy gate, `PolicyDecision`, `AuditEvent`
28+
29+
| `proof.v`
30+
| `src/api/zig/proof.zig`
31+
| SHA-256 proof envelope, UUID v4 generation, RFC 3339 timestamps
32+
33+
| `resolvers.v`
34+
| `src/api/zig/resolvers.zig`
35+
| Shared resolution logic for GraphQL/REST/gRPC
36+
37+
| `verb_governance.v`
38+
| `src/api/zig/verb_governance.zig`
39+
| Verb governance, trie-prefix matching, stealth timing jitter
40+
41+
| `redis_client.v`
42+
| `src/api/zig/redis_client.zig`
43+
| Raw RESP protocol over TCP, cache + audit log
44+
45+
| `librespeed_client.v`
46+
| `src/api/zig/librespeed_client.zig`
47+
| HTTP GET to LibreSpeed; shared `httpGet` helper
48+
49+
| `hyperglass_client.v`
50+
| `src/api/zig/hyperglass_client.zig`
51+
| HTTP POST to Hyperglass; BGP route hop parsing
52+
53+
| `smokeping_client.v`
54+
| `src/api/zig/smokeping_client.zig`
55+
| HTTP GET to SmokePing; CSV + RRD XML parsing
56+
57+
| `verisim_client.v`
58+
| `src/api/zig/verisim_client.zig`
59+
| VerisimDB bitemporal client; `dualLogAudit`; shared `auditEventToJson`
60+
61+
| _(inline in V modules)_
62+
| `src/api/zig/types.zig`
63+
| All shared domain types extracted into one place
64+
65+
|===
66+
67+
== Build
68+
69+
```bash
70+
# From repo root:
71+
zig build # → zig-out/bin/aerie-gateway
72+
zig build run # build + run
73+
zig build test # unit tests
74+
```
75+
76+
== Judgment Calls During Port
77+
78+
V uses garbage-collected heap strings everywhere. The Zig port uses fixed-size
79+
stack buffers (e.g. `[256]u8` for messages) and per-request arena allocators for
80+
dynamic data (audit log slices, GraphQL argument strings). This avoids heap
81+
fragmentation in a long-running server.
82+
83+
V's `TrieNode` with heap map children was replaced by a simple linear scan of
84+
the 6 hardcoded `RULES` with longest-prefix-wins logic. With only 6 rules this
85+
is O(6) and avoids pointer-heavy allocation. Phase 2 (YAML-loaded rules) may
86+
reinstate a trie.
87+
88+
`std.Thread.sleep` is used in place of V's `time.sleep` (the function moved in
89+
Zig 0.15.2). `std.ArrayList(T)` in 0.15.2 is the unmanaged form (allocator
90+
passed at each `append` call).
91+
92+
`v_grpc` dependency was dropped entirely. The Phase 1 gRPC layer (4-byte
93+
length-prefixed JSON) is implemented directly over `std.net.Stream` — no
94+
external gRPC crate needed at this phase.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= Aerie API — V-lang Implementation SIDELINED
4+
:toc:
5+
6+
== Status
7+
8+
The V-lang implementation of the Aerie API service (`src/api/v/`) is *sidelined*
9+
as of 2026-04-12 following the estate-wide V-lang ban (2026-04-10).
10+
11+
V-lang is banned across all hyperpolymath repositories. See
12+
link:https://github.com/hyperpolymath/standards/blob/main/VLANG-BAN.adoc[VLANG-BAN.adoc]
13+
for rationale and migration guidance.
14+
15+
== Migration Target
16+
17+
The replacement is a **Rust + Axum** API service with **Idris2 ABI / Zig FFI**
18+
for formally verified subsystems. Target location: `src/api/rust/`.
19+
20+
[cols="1,1,1"]
21+
|===
22+
| V module (sidelined) | Replacement | Status
23+
24+
| `main.v` | `src/api/rust/main.rs` | Planned
25+
| `proof.v` | `src/api/rust/proof.rs` | Planned — Idris2 ABI / Zig FFI
26+
| `policy.v` | `src/api/rust/policy.rs` | Planned
27+
| `resolvers.v` | `src/api/rust/resolvers.rs` | Planned
28+
| `verb_governance.v` | `src/api/rust/governance.rs`| Planned
29+
| `verisim_client.v` | VeriSimDB Rust client | See `nextgen-databases/verisimdb/connectors/clients/rust/`
30+
| `redis_client.v` | `deadpool-redis` crate | Planned
31+
| `librespeed_client.v`| Rust HTTP client | Planned
32+
| `hyperglass_client.v`| Rust HTTP client | Planned
33+
| `smokeping_client.v` | Rust HTTP client | Planned
34+
| `aerie.pb.v` | `tonic` gRPC codegen | Planned
35+
|===
36+
37+
== Note on V-grpc Dependency
38+
39+
The `v.mod` lists `v_grpc` as a dependency. The gRPC layer should be
40+
ported to `tonic` (Rust) or `grpc-web` (ReScript/Deno) depending on the
41+
transport requirements.
42+
43+
The V-lang source is retained in-tree for reference during migration but
44+
MUST NOT be compiled, deployed, or extended.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
//
3+
// hyperglass_client.v — HTTP Client for Hyperglass Probe
4+
//
5+
// Queries the Hyperglass BGP looking glass at HYPERGLASS_URL
6+
// (default http://hyperglass:8082) for route forensics data.
7+
// Parses responses into RouteHop records matching the Idris2 ABI
8+
// and protobuf definitions.
9+
//
10+
// Hyperglass provides BGP route path analysis, showing each hop
11+
// in the network path with ASN ownership and round-trip latency.
12+
13+
module main
14+
15+
import net.http
16+
import os
17+
import x.json2
18+
19+
// RouteHop represents a single hop in a BGP route path.
20+
// Fields match src/abi/Types.idr and src/api/proto/aerie.proto.
21+
pub struct RouteHop {
22+
pub:
23+
hop int
24+
ip string
25+
asn string
26+
rtt_ms f64
27+
}
28+
29+
// RouteForensicsPayload is the response wrapper for route forensics queries.
30+
pub struct RouteForensicsPayload {
31+
pub:
32+
target string
33+
path []RouteHop
34+
}
35+
36+
// get_hyperglass_url returns the configured Hyperglass endpoint.
37+
fn get_hyperglass_url() string {
38+
url := os.getenv('HYPERGLASS_URL')
39+
if url.len > 0 {
40+
return url
41+
}
42+
return 'http://hyperglass:8082'
43+
}
44+
45+
// get_route_forensics queries Hyperglass for BGP route information
46+
// to the specified target (IP address or hostname).
47+
//
48+
// Attempts to call the Hyperglass API for a BGP route lookup.
49+
// If the probe is unreachable, returns an empty path list with
50+
// the target preserved (rather than failing the entire request).
51+
//
52+
// In production, Hyperglass runs as a container in the aerie-net
53+
// network and exposes its API on internal port 80 (mapped to host 8082).
54+
pub fn get_route_forensics(target string) RouteForensicsPayload {
55+
base_url := get_hyperglass_url()
56+
57+
// Hyperglass API query endpoint
58+
response := http.fetch(http.FetchConfig{
59+
url: '${base_url}/api/query/'
60+
method: .post
61+
header: http.new_header(key: .content_type, value: 'application/json')
62+
data: '{"query_location":"","query_target":"${target}","query_type":"bgp_route"}'
63+
}) or {
64+
eprintln('hyperglass: probe unreachable at ${base_url}: ${err}')
65+
return RouteForensicsPayload{
66+
target: target
67+
path: []
68+
}
69+
}
70+
71+
if response.status_code != 200 {
72+
eprintln('hyperglass: unexpected status ${response.status_code} for target ${target}')
73+
return RouteForensicsPayload{
74+
target: target
75+
path: []
76+
}
77+
}
78+
79+
// Parse the Hyperglass response into RouteHop records
80+
parsed := json2.decode[json2.Any](response.body) or {
81+
eprintln('hyperglass: failed to parse response JSON')
82+
return RouteForensicsPayload{
83+
target: target
84+
path: []
85+
}
86+
}
87+
88+
// Extract route hops from the parsed JSON
89+
hops := parse_route_hops(parsed)
90+
91+
return RouteForensicsPayload{
92+
target: target
93+
path: hops
94+
}
95+
}
96+
97+
// parse_route_hops extracts RouteHop records from a Hyperglass JSON response.
98+
// The exact structure depends on the Hyperglass version and device type.
99+
fn parse_route_hops(data json2.Any) []RouteHop {
100+
mut hops := []RouteHop{}
101+
102+
// Hyperglass returns route data in various formats depending on
103+
// the router type. We attempt to extract hop-by-hop information.
104+
arr := data.as_array()
105+
for i, item in arr {
106+
obj := item.as_map()
107+
ip := if 'prefix' in obj {
108+
obj['prefix'] or { json2.Any('') }.str()
109+
} else if 'ip' in obj {
110+
obj['ip'] or { json2.Any('') }.str()
111+
} else {
112+
''
113+
}
114+
115+
asn := if 'as_path' in obj {
116+
obj['as_path'] or { json2.Any('') }.str()
117+
} else if 'asn' in obj {
118+
obj['asn'] or { json2.Any('') }.str()
119+
} else {
120+
''
121+
}
122+
123+
hops << RouteHop{
124+
hop: i + 1
125+
ip: ip
126+
asn: asn
127+
rtt_ms: 0
128+
}
129+
}
130+
131+
return hops
132+
}
133+
134+
// route_forensics_to_json serialises a RouteForensicsPayload to JSON.
135+
pub fn route_forensics_to_json(payload RouteForensicsPayload) string {
136+
mut hops_json := []string{}
137+
for h in payload.path {
138+
asn_field := if h.asn.len > 0 { '"${h.asn}"' } else { 'null' }
139+
rtt_field := if h.rtt_ms > 0 { '${h.rtt_ms}' } else { 'null' }
140+
hops_json << '{"hop":${h.hop},"ip":"${h.ip}","asn":${asn_field},"rttMs":${rtt_field}}'
141+
}
142+
return '{"target":"${payload.target}","path":[${hops_json.join(",")}]}'
143+
}

0 commit comments

Comments
 (0)