|
| 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 | += unified-zig-api — EXPLAINME |
| 4 | +:toc: |
| 5 | +:toc-placement: preamble |
| 6 | +:doctype: article |
| 7 | + |
| 8 | +== What this is |
| 9 | + |
| 10 | +`unified-zig-api` is the Idris2 ABI + Zig FFI replacement for all V-lang API |
| 11 | +and connector code in the hyperpolymath estate. It produces a single shared |
| 12 | +library (`libzig_api.so`) and static library (`libzig_api.a`) that expose a |
| 13 | +stable C ABI consumed by any language (Idris2, Rust, ReScript, Julia, etc.). |
| 14 | + |
| 15 | +V-lang was banned estate-wide on 2026-04-10. This library is the designated |
| 16 | +replacement for the 10 repos that still had V-lang connector or API server code. |
| 17 | + |
| 18 | +== Why it's built this way |
| 19 | + |
| 20 | +=== Idris2 ABI layer (`src/abi/`) |
| 21 | + |
| 22 | +Idris2's dependent types let us _prove_ things about the interface at |
| 23 | +compile time: |
| 24 | + |
| 25 | +* **Tag round-trips**: every `Result`, `ServiceId`, `ConnectorState`, and |
| 26 | + `Method` tag encodes and decodes back to the same value, proved by exhaustive |
| 27 | + `Refl` cases. |
| 28 | +* **Safe-path soundness**: `IsSafePath path allowlist` is a structural proof |
| 29 | + that `path` starts with an allowlist prefix. The `GnosisRenderCmd` record |
| 30 | + carries this proof as an auto-implicit argument — you cannot construct a |
| 31 | + render command for an unsafe path without discharging the proof. |
| 32 | +* **ABI injective**: `resultTagInjective` proves that equal Bits8 tags imply |
| 33 | + equal Result values, so the decode side cannot silently alias two variants. |
| 34 | + |
| 35 | +=== Zig FFI layer (`ffi/zig/src/`) |
| 36 | + |
| 37 | +Zig gives us native C ABI compatibility without overhead, comptime generics |
| 38 | +(the `Pool(T, capacity)` in `core.zig`), and `std.process.Child` for safe |
| 39 | +subprocess management. |
| 40 | + |
| 41 | +The four modules: |
| 42 | + |
| 43 | +[cols="1,3"] |
| 44 | +|=== |
| 45 | +| Module | Responsibility |
| 46 | + |
| 47 | +| `core.zig` |
| 48 | +| `Result` enum, thread-local error storage, generic `Pool(T, N)` with mutex |
| 49 | + |
| 50 | +| `process.zig` |
| 51 | +| Safe-path validation, `runProcess`, `runGnosis` (shells out to the `gnosis` binary) |
| 52 | + |
| 53 | +| `gnosis.zig` |
| 54 | +| HTTP/1.1 server pool (replaces `v-grpc` + `v-rest`). Handles `/render`, |
| 55 | + `/context`, `/health`, `/`, and gRPC-style `/gnosis.GnosisService/*` paths. |
| 56 | + |
| 57 | +| `connector.zig` |
| 58 | +| 64-slot HTTP connector pool for all 11 hyperpolymath services. |
| 59 | + `uapi_connector_call` is the universal request dispatcher. |
| 60 | +|=== |
| 61 | + |
| 62 | +== Routes served by the gnosis server |
| 63 | + |
| 64 | +[cols="2,1,3"] |
| 65 | +|=== |
| 66 | +| Path | Method | Description |
| 67 | + |
| 68 | +| `/render` | POST | Render a template via the `gnosis` binary |
| 69 | +| `/gnosis.GnosisService/Render` | POST | gRPC-style compat alias |
| 70 | +| `/context` | GET/POST | Retrieve SCM context |
| 71 | +| `/gnosis.GnosisService/Context` | POST | gRPC-style compat alias |
| 72 | +| `/health` | GET | 200 if serving, 503 if not |
| 73 | +| `/gnosis.GnosisService/Health` | POST | gRPC-style compat alias |
| 74 | +| `/` | GET | Service discovery JSON |
| 75 | +|=== |
| 76 | + |
| 77 | +== What it replaces |
| 78 | + |
| 79 | +`developer-ecosystem/v-grpc/` — gRPC server calling the gnosis binary + |
| 80 | +`developer-ecosystem/v-rest/` — REST server calling the gnosis binary |
| 81 | + |
| 82 | +Both upstream callers call the same `gnosis` binary. A single HTTP interface |
| 83 | +with both path families covers all existing call sites. |
| 84 | + |
| 85 | +== Directory layout |
| 86 | + |
| 87 | +---- |
| 88 | +zig-api/ |
| 89 | +├── src/abi/ Idris2 ABI definitions (proofs live here) |
| 90 | +│ ├── Types.idr Result codes, Handle, Slot |
| 91 | +│ ├── Http.idr Method, ServerState, HealthStatus, RouteDescriptor |
| 92 | +│ ├── Process.idr IsSafePath, ExecResult, GnosisRenderCmd |
| 93 | +│ ├── Connector.idr ServiceId, ConnectorState, GrooveManifest |
| 94 | +│ └── Foreign.idr %foreign declarations + IO wrappers |
| 95 | +├── ffi/zig/ |
| 96 | +│ ├── build.zig Produces shared + static libs, test runner |
| 97 | +│ └── src/ |
| 98 | +│ ├── lib.zig Root entry point, uapi_init / teardown / version |
| 99 | +│ ├── core.zig Result, error storage, Pool(T, N) |
| 100 | +│ ├── process.zig runProcess, runGnosis, safe-path validation |
| 101 | +│ ├── gnosis.zig HTTP/1.1 server pool (uapi_gnosis_*) |
| 102 | +│ └── connector.zig Service connector pool (uapi_connector_*) |
| 103 | +├── generated/abi/ |
| 104 | +│ └── zig_api.h C header (installed alongside the libraries) |
| 105 | +└── Justfile Build, test, and ABI-check recipes |
| 106 | +---- |
| 107 | + |
| 108 | +== Quick start |
| 109 | + |
| 110 | +[source,bash] |
| 111 | +---- |
| 112 | +# Build shared + static library |
| 113 | +just build |
| 114 | +
|
| 115 | +# Run all unit tests |
| 116 | +just test |
| 117 | +
|
| 118 | +# Check Idris2 proofs |
| 119 | +just check-abi |
| 120 | +
|
| 121 | +# Run integration tests (requires gnosis binary on $PATH) |
| 122 | +just test-integration |
| 123 | +---- |
| 124 | + |
| 125 | +== Consuming from Idris2 |
| 126 | + |
| 127 | +[source,idris] |
| 128 | +---- |
| 129 | +import ZigApi.ABI.Foreign |
| 130 | +
|
| 131 | +main : IO () |
| 132 | +main = do |
| 133 | + Ok <- uapiInit | _ => putStrLn "init failed" |
| 134 | + h <- gnosisCreate 8091 |
| 135 | + Ok <- gnosisStart h | _ => putStrLn "start failed" |
| 136 | + -- ... use the server ... |
| 137 | + gnosisStop h |
| 138 | + gnosisDestroy h |
| 139 | + uapiTeardown |
| 140 | +---- |
| 141 | + |
| 142 | +== Consuming from C |
| 143 | + |
| 144 | +[source,c] |
| 145 | +---- |
| 146 | +#include "zig_api.h" |
| 147 | +
|
| 148 | +int main(void) { |
| 149 | + uapi_init(); |
| 150 | + uint64_t h = uapi_gnosis_create(8091); |
| 151 | + uapi_gnosis_start(h); |
| 152 | + /* ... */ |
| 153 | + uapi_gnosis_stop(h); |
| 154 | + uapi_gnosis_destroy(h); |
| 155 | + uapi_teardown(); |
| 156 | + return 0; |
| 157 | +} |
| 158 | +---- |
| 159 | + |
| 160 | +== V-lang estate migration status |
| 161 | + |
| 162 | +Repos to migrate from V-lang to this library: |
| 163 | + |
| 164 | +* `developer-ecosystem/v-grpc` — replaced by `gnosis.zig` server |
| 165 | +* `developer-ecosystem/v-rest` — replaced by `gnosis.zig` server |
| 166 | +* `ambientops/` connectors — replace with `uapi_connector_create(UAPI_SERVICE_AMBIENT_OPS, ...)` |
| 167 | +* `nextgen-databases/verisimdb/` connectors — `UAPI_SERVICE_VERISIMDB` |
| 168 | +* `proven/` V connector stubs — replace with connector pool calls |
| 169 | +* `007/` V helper modules — replace with `uapi_connector_*` or process calls |
| 170 | +* `standards/lol/` V stubs — replace per sub-project |
| 171 | +* `odds-and-sods-package-manager/opsm_ex/` — replace V HTTP client |
| 172 | + |
| 173 | +See `.machine_readable/6a2/STATE.a2ml` for detailed migration tracking. |
0 commit comments