Skip to content

Commit c33c2b6

Browse files
hyperpolymathclaude
andcommitted
docs: update playbook for single-port + set_handler canonical shape
Document the new uapi_gnosis_set_handler + uapi_gnosis_write_response edge hook API added 2026-04-17. Add a "Canonical single-port shape" subsection in "From another Zig project" with a complete annotated example including the [*c] → * ptrCast pattern. Update the gnosis.zig module description, the "Wiring a New Edge Repo" checklist (no more own accept loops), and the status table. Reference aerie and lol/api/zig-gateway as live examples. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9a2edcb commit c33c2b6

1 file changed

Lines changed: 97 additions & 2 deletions

File tree

UNIFIED-ZIG-API-STACK.adoc

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ Zig 0.15.2. Produces `libzig_api.so` (shared) and `libzig_api.a` (static).
138138
| HTTP/1.1 server pool. Replaces v-grpc + v-rest. Handles `/render`,
139139
`/context`, `/health`, `/`, and gRPC-style `/gnosis.GnosisService/*` paths.
140140
`uapi_gnosis_{create,start,stop,destroy,state,health}`.
141+
**Edge hook API (2026-04-17):** `uapi_gnosis_set_handler(handle, fn_ptr)` and
142+
`uapi_gnosis_write_response` support single-port + path-routed edge dispatch;
143+
see §"Canonical single-port shape" below. When a hook is registered, gnosis
144+
calls the edge fn for every request instead of the built-in routes — no
145+
separate accept loop needed in the edge repo.
141146

142147
| `connector.zig`
143148
| 64-slot HTTP connector pool for 11 hyperpolymath services.
@@ -187,7 +192,79 @@ main = do
187192

188193
=== From another Zig project
189194

190-
Option A — link the shared library and `@cImport` the header:
195+
==== Canonical single-port shape (2026-04-17)
196+
197+
The standard pattern for Zig edge repos is one gnosis pool slot plus
198+
`uapi_gnosis_set_handler`. The edge repo *does not* bind its own sockets or
199+
run its own accept loops — gnosis owns the listener; the edge supplies the
200+
dispatch function.
201+
202+
[source,zig]
203+
----
204+
const c = @cImport(@cInclude("zig_api.h"));
205+
206+
// GnosisRequest and GnosisResponse are defined in zig_api.h.
207+
// @cImport translates C pointer types to [*c] in Zig.
208+
// Convert to regular pointers at the top of the handler for ergonomic field
209+
// access:
210+
//
211+
// const resp: *c.GnosisResponse = @ptrCast(resp_c);
212+
// const req: *const c.GnosisRequest = @ptrCast(req_c);
213+
//
214+
export fn myEdgeHandler(
215+
req_c: [*c]const c.GnosisRequest,
216+
resp_c: [*c]c.GnosisResponse,
217+
) callconv(.c) void {
218+
const resp: *c.GnosisResponse = @ptrCast(resp_c);
219+
const req: *const c.GnosisRequest = @ptrCast(req_c);
220+
221+
const path = std.mem.span(req.path);
222+
223+
if (std.mem.startsWith(u8, path, "/api/v1/")) {
224+
const body = "{\"status\":\"ok\"}";
225+
c.uapi_gnosis_write_response(resp_c, 200, "application/json",
226+
body, body.len);
227+
} else {
228+
const e = "{\"error\":\"not found\"}";
229+
c.uapi_gnosis_write_response(resp_c, 404, "application/json",
230+
e, e.len);
231+
}
232+
_ = resp; _ = req; // suppress unused-var warnings if not needed
233+
}
234+
235+
pub fn main() !void {
236+
_ = c.uapi_init();
237+
defer c.uapi_teardown();
238+
239+
const handle = c.uapi_gnosis_create(4000);
240+
// Register the edge handler BEFORE uapi_gnosis_start.
241+
_ = c.uapi_gnosis_set_handler(handle, &myEdgeHandler);
242+
_ = c.uapi_gnosis_start(handle);
243+
defer c.uapi_gnosis_stop(handle);
244+
defer c.uapi_gnosis_destroy(handle);
245+
246+
while (c.uapi_gnosis_state(handle) == c.UAPI_SERVER_LISTENING) {
247+
std.Thread.sleep(std.time.ns_per_s);
248+
}
249+
}
250+
----
251+
252+
Key rules for the edge hook:
253+
254+
* Call `uapi_gnosis_set_handler` *after* `uapi_gnosis_create` and *before*
255+
`uapi_gnosis_start`. Calling after start returns `UAPI_ERR`.
256+
* Pass `NULL` as `handler_fn` to revert to built-in gnosis routes.
257+
* `GnosisRequest` fields (`method`, `path`, `body_ptr`, `body_len`) are valid
258+
only for the duration of the handler call — copy any data you need to keep.
259+
* `GnosisResponse` fields must be fully populated before the handler returns.
260+
Use `uapi_gnosis_write_response` as a convenience or fill the struct directly.
261+
* `body_ptr` in `GnosisResponse` must remain valid until `uapi_gnosis_write_response`
262+
returns — a module-level or stack buffer that outlives the call is fine;
263+
an arena-allocated slice freed before return is not.
264+
265+
==== Option B — connector usage only
266+
267+
Link the shared library and `@cImport` the header:
191268

192269
[source,zig]
193270
----
@@ -203,7 +280,7 @@ pub fn main() !void {
203280
}
204281
----
205282

206-
Option B — add `libzig_api` as a `build.zig` dependency and import symbols
283+
Option C — add `libzig_api` as a `build.zig` dependency and import symbols
207284
directly from the Zig modules (internal use only; C ABI is the stable surface).
208285

209286
=== From Rust
@@ -242,6 +319,16 @@ const zig_api = b.addSharedLibraryFromPath(
242319
exe.linkLibrary(zig_api);
243320
exe.addIncludePath(.{ .cwd_relative = PATH_TO_GENERATED_ABI });
244321
----
322+
. *Use the canonical single-port + set_handler shape* (see §"Canonical
323+
single-port shape" above) for all HTTP-serving edge repos. **Do not** bind
324+
your own sockets or run your own accept loops — gnosis owns the listener.
325+
Register your dispatch function with `uapi_gnosis_set_handler` before
326+
calling `uapi_gnosis_start`. Path-route all protocols (REST, GraphQL,
327+
gRPC-JSON) inside a single edge handler. Reference implementations:
328+
- `standards/lol/api/zig-gateway/src/main.zig` — LoL gateway (v0.2.0,
329+
single-port, path-routed, 3-protocol)
330+
- `aerie/src/api/zig/main.zig` — Aerie gateway (single-port, path-routed,
331+
REST + GraphQL + gRPC-JSON)
245332
. *Place repo-specific Idris2 ABI proofs in `src/abi/` of the edge repo.*
246333
Import `ZigApi.ABI.Types` and `ZigApi.ABI.Connector` from `zig-api.ipkg`
247334
(add it as a dependency in the edge repo's `.ipkg`). Extend, do not fork.
@@ -316,6 +403,14 @@ The trigger was the estate-wide V-lang ban on 2026-04-10 (V-lang detected via
316403
| Idris2 ABI fully proved; Zig runtime builds and passes unit tests;
317404
integration tests require `gnosis` binary on `$PATH`.
318405

406+
| `uapi_gnosis_set_handler` + `uapi_gnosis_write_response`
407+
| ACTIVE (2026-04-17)
408+
| Edge hook API. Enables single-port + path-routed dispatch for all edge
409+
repos. `set_handler` registers a C-ABI `GnosisHandlerFn` before start;
410+
`write_response` is a convenience fill helper. Idris2 ABI:
411+
`ZigApi.ABI.Foreign.prim__gnosisSetHandler`. Header: `zig_api.h` lines
412+
197–212.
413+
319414
| `zig-groove-bridge`
320415
| PARTIAL
321416
| Dodeca-API + discovery logic written; attachment / full Groove manifest

0 commit comments

Comments
 (0)