@siax/event-client (0.1.0)
Installation
@siax:registry=https://git.cloud.siax.io/api/packages/sax3l/npm/npm install @siax/event-client@0.1.0"@siax/event-client": "0.1.0"About this package
@siax/event-client
P012 (Network/DB/Event/Idempotency/SLO Standards, new P000-P049 masterplan):
createEventClient() — validates a candidate event against @siax/schemas'
siax.event schema and, only if valid, POSTs it to AUD0
(POST /v1/aud0/events). Zero external dependencies — @siax/schemas is a
workspace-internal link only. Node ESM (.mjs), Node ≥ 20 — same convention
as @siax/schemas, @siax/secret-resolver and @siax/write-guard.
Why this package exists
P012 recon (2026-09) found schema/siax.event.schema.json in exactly the
same state P002 found siax.secret-reference in: a real, well-formed,
unit-tested JSON Schema with zero runtime consumers anywhere in the
estate. grep -rl "@siax/schemas" across every local clone under
~/siax-platforms-work/ returned hits only inside siax-standard's own
packages — no aud0, act0, h0st, cl0ud, n0d, inf0, st0re, 0pt,
b00k, vehiq-infra, … import it.
It was worse than a plain stub, though: a real, ~20-producer wire
contract already existed, just not captured by this schema. AUD0's own
src/schemas/event.ts (zod, backing its real POST /v1/aud0/events Fastify
route) defines the actual shape: event_type, tenant_id, project_id?, actor_id?, app_id, capability_id, resource_type, resource_id, risk_level, source?, payload?, created_at?, event_id?, plus the P0 context fields added
later (sql/032_audit_event_context.sql): occurred_at, correlation_id, causation_id, trace_id, purpose, policy_decision, input_refs, output_refs, result, idempotency_key. act0, h0st, doc0, cal0, n0d, 0pt,
id0, n0tify, c0nf0rm, inf0, siax-templates, and more each
hand-roll a local copy of this shape in their own aud0-client.ts — ACT0's
own comment says it best: "Kept as a separate copy here … there is no
automated sync." Even AUD0's own JSON Schema mirror
(src/schemas/event.schema.json) had drifted from its own zod source of
truth, missing every one of the P0 context fields.
schema/siax.event.schema.json has been reconciled field-for-field to
that real contract (snake_case wire shape) instead of the old P003 camelCase
shape (id/type/tenantId/actor/occurredAt/riskLevel) nothing ever emitted.
This package is the first thing in the estate that actually validates an
event against that reconciled schema before sending it anywhere.
The one behavior change from a hand-rolled client
emit(event) validates BEFORE doing anything else. An invalid event throws
EventValidationError and the network call never happens — fail-closed,
the same discipline @siax/secret-resolver and @siax/write-guard already
apply to their own side effects.
Reference consumer: @siax/write-guard
packages/write-guard/src/aud0-client.mjs used to BE a hand-rolled AUD0
client (deliberately reimplemented rather than imported, because
siax.event was a stub at the time — see that file's git history). Now
that the schema is real, write-guard's createAud0Client() delegates
to this package — same public API (.ingest(event), same error class
names, re-exported not reimplemented), but every event write-guard sends
(including privileged-override.mjs's real, production-code-path audit
events for P000-E's override flow) is now validated against siax.event
first. This is the reference-service wiring an unconsumed schema does not
get for free — see packages/write-guard/README.md and
packages/write-guard/test/aud0-client.test.mjs's new
Aud0EventValidationError test for the proof.
Core API
import { createEventClient, EventValidationError } from '@siax/event-client';
const client = createEventClient({
projectId: undefined, // not applicable here — see baseUrl/token below
token: process.env.EVENT_CLIENT_AUD0_TOKEN,
});
try {
const stored = await client.emit({
event_type: 'capability.invoked',
tenant_id: 't_vehiq',
app_id: 'my-service',
capability_id: 'n0d.run_job',
resource_type: 'job',
resource_id: 'job-42',
risk_level: 'L1',
});
console.log(stored.event_id); // AUD0-assigned, if omitted on input
} catch (err) {
if (err instanceof EventValidationError) {
// the event never left this process — err.errors names exactly what's wrong
}
throw err;
}
Configuration (env vars)
| Env var | Purpose |
|---|---|
EVENT_CLIENT_AUD0_API_URL (falls back to WRITE_GUARD_AUD0_API_URL for write-guard compat) |
AUD0 base URL. Defaults to https://api.aud0.siax.io/v1/aud0. |
EVENT_CLIENT_AUD0_TOKEN (falls back to WRITE_GUARD_AUD0_TOKEN) |
Bearer token for the AUD0 API. |
Honest status — what is real vs. not yet verified
Same convention as packages/secret-resolver/README.md and the old
aud0-client.mjs's "HONEST STATUS" note: whether
https://api.aud0.siax.io/v1/aud0 is actually deployed and reachable was
not verified from this repo/pass. Every test in test/*.test.mjs runs
against an injected fake fetch — zero real network calls. A follow-up
pass must run one real request against the actual deployed AUD0 instance
before this is trusted in a real boot path.
What "reference service uses the library" means here, precisely
write-guard consuming this package (within siax-standard) is real,
tested, end-to-end wiring — but it is not the same claim as "AUD0 or
another estate repo imports @siax/event-client in production". No repo
outside siax-standard does yet (confirmed: zero hits for
@siax/event-client anywhere else under ~/siax-platforms-work/, same as
@siax/secret-resolver's status after P002). Closing that estate-wide gap
— migrating ACT0's or AUD0's own hand-rolled client to this package, or at
minimum pointing their event.schema.json at this reconciled shape — is a
clear, actionable follow-up this pass flags rather than silently claims
done.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @siax/schemas | workspace:* |