commit f850002f1c562838f67cd40ae1403cfb85949203 Author: George Lambert Date: Mon Sep 14 13:50:45 2026 -0400 Add developer credentials: URLs, lab keys, NATS token, staff IAM, git diff --git a/00-sources.md b/00-sources.md new file mode 100644 index 0000000..25e9d8d --- /dev/null +++ b/00-sources.md @@ -0,0 +1,55 @@ +# Sources (other documentation that names routes) + +This inventory is compiled from code and from these docs. Do not invent extra `verae.*` names. + +## Master maps + +| Path | What it names | +|------|----------------| +| `packages/docs-master/modules-and-nats.md` | Module catalog, subject table, HTTPS vs NATS | +| `packages/docs-master/MESSAGE-FLOWS.md` | Create/wait/includeAttached/batch Merkle | +| `packages/docs-master/archive-nats.md` | Archive put/query/reply | +| `docs/02-architecture/nats-subjects.md` | `verae.zapier.*` payloads + streams | +| `docs/02-architecture/modules-and-nats.md` | Same catalog as docs-master | +| `docs/02-architecture/overview.md` | Poller / events / webhooks | +| `packages/overview/03-nats-cluster.md` | Cluster URLs, who may connect | +| `packages/overview/06-address-routing.md` | Address pattern + plane mapping | +| `packages/verae-ops/06-linking-services.md` | How services attach | +| `packages/verae-nats-cluster/scripts/ensure-streams.sh` | Live stream create | +| `packages/verae-nats-cluster/conf/nats.conf.tmpl` | `:4222` / `:6222` / `:7422` / monitor `:8222` | +| `packages/verae-nats-cluster/conf/public-edge.conf.tmpl` | Public leaf `:4222`, WS `:14422` | +| `packages/verae-fleet/overlays/ns1/fleet.json` | Enabled vs in-process workers | +| `packages/verae-keep` `units.px-worker.json` | Worm/tree on LXC 510 | +| `packages/verae-zapier-middleware/src/nats/subjects.js` | Product subjects + consumers | +| `packages/zappier-account-balance/src/subjects.js` | Billing subjects | +| `packages/verae-access-authz/src/subjects.js` + `policy.js` | Authz + plane allow lists | +| `packages/zappier/src/billing-nats.ts` | Edge billing client | + +## Per-package `NATS.md` (Forgejo module copies also live under `docs-master/modules/`) + +| Package | Addresses | +|---------|-----------| +| `verae-zapier-middleware` | `jobs.watch` out, `jobs.events` in, archive.query via aggregator | +| `verae-jobs-events` | `jobs.events`; exclusive → `verae.internal.jobs.events` | +| `verae-request-splitter` | `archive.put` | +| `verae-archive-worm` | `archive.put` / `query` / `reply.` | +| `verae-tree-node` | same, `kind: tree` | +| `verae-archive-aggregator` | `query` out, `reply.` in | +| `zappier-account-balance` | `verae.billing.*` | +| `zappier` (edge) | package `NATS.md` is stale (“does not publish”); **code** publishes usage/payment/credit/customer.put when `NATS_URL` is set. Use `docs-master/modules/zappier/NATS.md` | +| `zappier-customer-service` | `statement.get`, `balance.adjust` | +| `zappier-sales-pricing` | `statement.get` | +| `zappier-accounting-export` | `statement.get` | +| `zappier-identity` | `identity.bind` / `lookup` | +| `verae-access-authz` | `authz.check` / `authz.deny` | +| `verae-access-web` | `access.web.in` / `access.web.billing.*` | +| `verae-access-api` | `access.api.*` | +| `verae-access-zapier` | `access.zapier.*` then HTTPS to middleware | +| `verae-access-leaf` | `access.leaf.in` | +| `verae-access-staff` | `access.staff.*` | +| `verae-nats-process` | `verae.example.process.*` (template) | +| `verae-nats-accounts` | policy only (INTERNAL vs LEAF) | +| `verae-zapier-simulator` | in-process replay of product addresses | +| `verae-keep` / `verae-fleet` / `verae-uptime` / `verae-backup` | no product subjects | + +Simulator `src/pipeline.js` and `src/monitors.js` list the same product addresses for traces; they are not live subscribers. diff --git a/01-nats-routes.md b/01-nats-routes.md new file mode 100644 index 0000000..34a4758 --- /dev/null +++ b/01-nats-routes.md @@ -0,0 +1,106 @@ +# Designed vs running NATS routes + +**Designed** = named in docs or in code. +**Running** = a process on NS1 is subscribed or publishing it **today**, and (for durable work) a JetStream stream exists. + +Cluster name `verae`. Internal clients use the three LXC on `vmbr1`. Public edge is a **leaf**, not a second cluster. + +Legend: **JS** = JetStream stream; **RR** = core request-reply; **pub** = core or JS fire-and-forget; **Q** = queue group. + +## Transport (TCP / leaf / public) + +These are NATS **server** routes, not `verae.*` subjects. + +| Endpoint | Designed | Running | Who | +|----------|----------|---------|-----| +| `10.10.10.21–23:4222` client | yes | **yes** | middleware, access-*, billing, worm/tree, identity, CS/sales/acct | +| `10.10.10.21–23:6222` cluster routes | yes | **yes** (private `vmbr1` only) | nats-a/b/c to each other | +| `10.10.10.21–23:7422` leaf hub | yes (public-edge) | **yes** | public edge remotes | +| `127.0.0.1:8222` monitor on each LXC | yes | **yes** | operators on the guest | +| `70.88.205.138:4222` public client | yes (token) | **yes** | leaf edge; **not** Zapier | +| `127.0.0.1:14422` WS on edge | yes | **yes** | Caddy → `wss://nats.zapier.georgelambert.org` | +| `127.0.0.1:8229` edge monitor | yes | **yes** | host loopback | +| `:6222` / `:7422` / `:8222` on `vmbr0` | **no** | **must stay closed** | — | +| Host leftover `127.0.0.1:4222` (docker-proxy) + `10.10.10.1:4222` socat | lab leftover | present | **do not** point product `NATS_URL` here | +| MQTT `:1883` | study only | **removed** | — | + +Internal keep/fleet/middleware URL: + +```text +nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +``` + +Public (token in `/etc/nats/public-edge.token` on NS1): `nats://@70.88.205.138:4222`. Isolation on the hub is still `$G` + private LAN. nkeys/mTLS sketched in `verae-nats-accounts`, **not** flipped. + +## JetStream streams (running) + +Created by `verae-nats-cluster/scripts/ensure-streams.sh` (middleware `ensureStreams` also creates the first three). + +| Stream | Subjects | Replicas | Storage | Who consumes | +|--------|----------|----------|---------|--------------| +| `ZAPIER_JOBS` | `verae.zapier.jobs.watch` | 3 | file | job-poller (in middleware process), Q `job-poller` | +| `ZAPIER_EVENTS` | `verae.zapier.jobs.events` | 3 | file | middleware wait + webhook router (`event-webhook-router`); optional exclusive `jobs-events` | +| `ZAPIER_WEBHOOKS` | `verae.zapier.webhooks.deliver` | 3 | file | webhook-deliver (in middleware), Q `webhook-deliver` | +| `ZAPIER_USAGE` | `verae.zapier.usage` | 3 | file | **no durable consumer** (`USAGE_WRITER` named only) | +| `VERAE_ARCHIVE` | `verae.archive.>` | 3 | file | worm + tree on LXC 510 (keep); aggregator **not** fleet-enabled | + +Billing is **core request-reply / pub**, not a JetStream stream. + +## Address inventory + +| Address | Kind | Designed | Running | Notes | +|---------|------|----------|---------|-------| +| `verae.zapier.jobs.watch` | JS work Q `job-poller` | yes | **yes** | middleware publishes; in-process poller consumes | +| `verae.zapier.jobs.events` | JS events | yes | **yes** | poller publishes; wait() subscribes; webhook worker routes | +| `verae.zapier.webhooks.deliver` | JS work Q `webhook-deliver` | yes | **yes** | same Node process as HTTP | +| `verae.zapier.usage` | JS optional | yes | stream **yes**, consumer **no** | middleware entitlement is **local files** | +| `verae.internal.jobs.events` | core republish | yes (`jobs-events` exclusive) | **partial** | only if `JOBS_EVENTS_EXCLUSIVE=1` | +| `verae.billing.statement.get` | RR Q `account-balance` | yes | **yes** | portal, CS, sales, accounting, access-web/api | +| `verae.billing.balance.get` | RR | yes | **yes** | account-balance | +| `verae.billing.balance.adjust` | RR | yes | **yes** | CS credits, portal reload | +| `verae.billing.usage.recorded` | pub | yes | **yes if edge `NATS_URL` set** | `zappier/src/app.ts` `onUsage` → `natsPublish`; account-balance subscribes | +| `verae.billing.payment.recorded` | pub | yes | **yes if `NATS_URL`** | portal reload (`portal.ts`) | +| `verae.billing.credit.applied` | pub | yes | **yes if `NATS_URL`** | admin/CS (`admin.ts`) | +| `verae.billing.customer.put` | pub | yes | **yes if `NATS_URL`** | `ledger.ts`; account-balance applies | +| `verae.access.authz.check` | RR Q `access-authz` | yes | **yes** | fleet keepFloor | +| `verae.access.authz.deny` | pub audit | yes | **yes** (on deny) | no SIEM sink | +| `verae.access.web.in` / `verae.access.web.billing.*` | ingress | yes | **yes** | mapped after authz, e.g. `verae.access.web.billing.statement.get` | +| `verae.access.api.*` | ingress | yes | **yes** | | +| `verae.access.zapier.*` | ingress | yes | **yes** | HTTPS plane; Zapier never speaks NATS | +| `verae.access.leaf.in` / `verae.access.leaf.*` | ingress | yes | **yes** | no billing | +| `verae.access.staff.*` | ingress | yes | **yes** | CS/sales/accounting | +| `verae.identity.bind` | RR Q `identity` | yes | **yes** | fleet | +| `verae.identity.lookup` | RR Q `identity` | yes | **yes** | | +| `verae.archive.put` | JS (`VERAE_ARCHIVE`) | yes | **partial** | splitter **in-process** in middleware; worm/tree on 510 consume | +| `verae.archive.query` | broadcast (no Q) | yes | **partial** | live aggregator hook **TBD** in middleware (simulator has it) | +| `verae.archive.reply.` | replies on bloom hit | yes | **partial** | worm/tree on 510 can reply; aggregator not fleet-enabled | +| `verae.splitter.in` | future | yes (docs) | **no** | splitter stays a library | +| `verae.example.process.in` / `.out` / `.reply.*` | template | yes | **no** | `verae-nats-process` is a model | +| `bench.*` | lab only | n/a | ephemeral | speed studies; not product | + +Plane mapping (`verae-access-authz`): internal `verae.billing.statement.get` + plane `web` → ingress `verae.access.web.billing.statement.get`. Planes never publish internal subjects until `authz.check` returns allow. + +## Durable consumers (named in middleware) + +| Name | Stream / subject | Running | +|------|------------------|---------| +| `job-poller` | `ZAPIER_JOBS` / `jobs.watch` | **yes** (in-process) | +| `event-webhook-router` | `ZAPIER_EVENTS` / `jobs.events` | **yes** (in-process) | +| `webhook-deliver` | `ZAPIER_WEBHOOKS` / `webhooks.deliver` | **yes** (in-process) | +| `usage-writer` | `ZAPIER_USAGE` / `verae.zapier.usage` | **no** | +| `jobs-events-exclusive` | `ZAPIER_EVENTS` | **no** unless `JOBS_EVENTS_EXCLUSIVE=1` | + +## Fleet overlay vs in-process + +`packages/verae-fleet/overlays/ns1/fleet.json` (2026-09-14): + +| Service | fleet `enabled` | Actual lab | +|---------|-----------------|------------| +| middleware-http | true | **running**; starts job-poller + webhook workers in-process when `NATS_ENABLED` | +| job-poller | **false** | covered by middleware; do not spawn a second copy | +| webhook-deliver | **false** | same | +| archive-aggregator | **false** | **not** running as a fleet worker | +| archive-worm / tree-node | **false** on host fleet | **running on LXC 510** via keep `units.px-worker.json` (1 worm + 1 tree) | +| account-balance, access-*, identity, jobs-events, CS/sales/acct, staff-iam/session, zappier-edge | true keepFloor | keepFloor | +| zapier-simulator | true, keepFloor false | optional | +| nats | managed false | 3-node cluster + leftover host loopback | diff --git a/02-endpoint-io.md b/02-endpoint-io.md new file mode 100644 index 0000000..38d1e5f --- /dev/null +++ b/02-endpoint-io.md @@ -0,0 +1,154 @@ +# Endpoint I/O + +Each product NATS address: **what it is for**, **who sends**, **who receives**, **how to attach**. + +Connect with a **long-lived** client (reconnect-per-message is the anti-pattern): + +```text +NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +``` + +Public edge (token in `/etc/nats/public-edge.token` on NS1): `nats://@70.88.205.138:4222`. Do not put that URL in Zapier or in Verae. + +--- + +## `verae.zapier.jobs.watch` + +| | | +|--|--| +| For | Work queue: “poll Verae until this `jobId` is terminal.” | +| In from | middleware `enqueueWatch` after `POST /api/timestamp` (or mock) returns `jobId` | +| Out to | job-poller worker (queue `job-poller`, durable on `ZAPIER_JOBS`) | +| Then | poller calls Verae `GET /api/status/{jobId}` over **HTTPS**, not NATS | + +Payload: `tenantId`, `veraeUserId`, `jobId`, `tokenRef`, `enqueuedAt`, `attempt`, `maxAttempts`, `intervalMs`, `traceId`. JWT stays in middleware (`tokenRef`); never on Zapier. + +**Connect Verae-side:** do **not** subscribe this from `api.veraetime.net`. The poller is the Verae HTTP client. To add another watcher, start another `job-poller` consumer in the same queue group. + +--- + +## `verae.zapier.jobs.events` + +| | | +|--|--| +| For | Terminal outcomes: `timestamp.completed` / `failed` / `timeout` | +| In from | job-poller after Verae status is not `pending` | +| Out to | (1) middleware `waitForJobEvent` (wait HTTP); (2) webhook router → `webhooks.deliver`; (3) optional `verae-jobs-events` mailbox `:3030` | + +If `JOBS_EVENTS_EXCLUSIVE=1`, only `verae-jobs-events` is the durable consumer and republishes `verae.internal.jobs.events`. Default lab: middleware wait + webhook worker share the stream. + +Payload: `event`, `tenantId`, `jobId`, `status`, `traceId`, `emittedAt`. + +--- + +## `verae.zapier.webhooks.deliver` + +| | | +|--|--| +| For | HTTPS POST to `hooks.zapier.com` (REST Hook) | +| In from | webhook worker on completed/failed events | +| Out to | Zapier cloud **HTTPS**, never NATS | + +Payload: `hookId`, `tenantId`, `targetUrl`, `event`, `payload`, `attempt`, `traceId`. Slow consumer: keep replica floors on the worker if you split it out of middleware. + +--- + +## `verae.zapier.usage` + +| | | +|--|--| +| For | Optional meter of Zap actions on NATS (JetStream) | +| In from | designed: middleware / edge | +| Out to | designed: `USAGE_WRITER` → books | +| Running | **stream exists; no writer; middleware uses local usage files** | + +Prepaid meter that **is** wired uses `verae.billing.usage.recorded` (core pub), not this stream. Finish: either publish here from `recordUsage` or drop the stream. + +--- + +## Billing (`verae.billing.*`) — account-balance source of truth + +Request-reply unless noted. Queue `account-balance`. **Not** JetStream. + +| Address | For | In from | Out to | +|---------|-----|---------|--------| +| `statement.get` | prepaid, credits, usage, payments | portal, CS, sales, accounting, access-web/api | reply to requester | +| `balance.get` | cents only | internal | reply | +| `balance.adjust` | CS credit or portal reload | CS, access-web (reload kind only) | books + reply | +| `usage.recorded` | meter pub | zappier-edge `meter()` `onUsage` when `NATS_URL` set | account-balance subscriber (debits prepaid) | +| `payment.recorded` | reload/invoice pub | portal | books | +| `credit.applied` | goodwill pub | CS/admin | books | +| `customer.put` | upsert customer | edge ledger | books | + +Authz **must** pass `verae.access.authz.check` before these from a public plane. Leaf plane **cannot** call billing (policy). + +Middleware `recordUsage` still writes **local tenant files** and does **not** publish `usage.recorded`. Two meters until that is unified. + +--- + +## Access planes + +| Address | For | In | Out | +|---------|-----|----|-----| +| `verae.access.authz.check` | allow/deny `{ plane, subject, principal }` | every plane + edge `billing-nats.ts` | `{ allow, reason }`; deny also pubs `authz.deny` | +| `verae.access.authz.deny` | audit | authz on deny | **nowhere yet** (no SIEM) | +| `verae.access.web.in` / `verae.access.web.billing.*` | browser portal | access-web HTTP | `statement.get` / `balance.adjust` kind=reload after authz | +| `verae.access.api.*` | customer `x-api-key` | access-api | statement; HTTPS to middleware for jobs | +| `verae.access.zapier.*` | Zapier HTTPS only | access-zapier | jobs.watch **via HTTP to middleware**, not Zapier NATS; usage.recorded allowed by policy | +| `verae.access.leaf.in` | S2S `{ token, target, payload }` | remote leaf | archive + jobs after authz; **never billing** | +| `verae.access.staff.*` | CS/sales/accounting | access-staff | billing after authz | + +Ingress mapping: `accessAddress(plane, 'verae.billing.statement.get')` → `verae.access..billing.statement.get`. + +--- + +## Identity + +| Address | For | In | Out | +|---------|-----|----|-----| +| `verae.identity.bind` | bind email/customerId → `veraeUserId` | portal / middleware | reply | +| `verae.identity.lookup` | lookup by customerId or veraeUserId | same | reply | + +Queue `identity`. Fleet keepFloor. + +--- + +## Archive / tree + +| Address | For | In | Out | +|---------|-----|----|-----| +| `verae.archive.put` | WORM/tree write (`kind`: publicMeta / privateMeta / file / tree) | splitter (in-process from middleware today) | every worm/tree (JS) | +| `verae.archive.query` | broadcast lookup | aggregator | **all** worm/tree, **no** queue group | +| `verae.archive.reply.` | bloom **hit only** | worm/tree | aggregator | + +Silence = miss. Chain seal does not wait on archive. Bytes never go to the chain. + +--- + +## Template / unused + +| Address | For | In | Out | Running | +|---------|-----|----|-----|---------| +| `verae.splitter.in` | future split-out splitter process | middleware | splitter | **no** | +| `verae.example.process.in` | template worker | any | `example-process` Q | **no** | +| `verae.example.process.out` | template fan-out | template | peers | **no** | +| `verae.example.process.reply.` | template reply | template | requester | **no** | +| `verae.internal.jobs.events` | exclusive mailbox republish | jobs-events | wait/webhook | **no** unless env | + +--- + +## How to connect **Verae API** to NATS + +`api.veraetime.net` is **HTTPS**. It does not subscribe. The glue is middleware: + +1. Set `VERAE_API_BASE_URL=https://api.veraetime.net` and `MOCK_VERAE=false`. +2. Middleware login (username/password) → JWT stored as `tokenRef`. +3. `POST /api/timestamp` → 202 `{ jobId }`. +4. Publish `verae.zapier.jobs.watch`. +5. Poller `GET /api/status/{jobId}` until `completed` / `failed`. +6. Publish `verae.zapier.jobs.events`. +7. Wait HTTP and/or REST Hook. + +Do **not** point Verae at `NATS_URL`. Do **not** put NATS in Zapier env. A new worker that must see seals uses `jobs.events` or HTTP `GET /zapier/v1/status/{jobId}`. + +Template for a new addressed process: clone `verae-nats-process`, rename `verae.example.process.*`, add the subject to `verae-access-authz` policy for the plane that may call it. diff --git a/03-zapier-verae-path.md b/03-zapier-verae-path.md new file mode 100644 index 0000000..a0c16de --- /dev/null +++ b/03-zapier-verae-path.md @@ -0,0 +1,125 @@ +# Zapier → Verae API → Zapier + +How a timestamp job is submitted, watched, and returned. Lab today: `MOCK_VERAE=true`. Live path is the same HTTP contract with `MOCK_VERAE=false`. + +## Rule + +```text +Zapier cloud --HTTPS only--> zap.zapier.georgelambert.org (access-zapier) + | + v HTTPS + middleware :3100 /zapier/v1 + | + +---------------+----------------+ + | HTTPS | NATS (private cluster) + v v + api.veraetime.net jobs.watch → poller + POST /api/timestamp GET /api/status/{jobId} + GET /api/status/{jobId} | + POST /api/verify v + jobs.events + | + +--------------------+--------------------+ + | wait subscriber | webhook-deliver + v v + HTTP response to Zap POST hooks.zapier.com +``` + +Zapier never has `NATS_URL`. Verae never has `NATS_URL`. + +## 1. Submit (async create) + +1. Zap action **Create Timestamp** (or curl) `POST https://zap.zapier.georgelambert.org/zapier/v1/timestamp` with portal API key `zmw_…` (not a Verae JWT). +2. access-zapier / edge: authz, then `meter()` records cents; if `NATS_URL` is set, `natsPublish(verae.billing.usage.recorded)`. +3. Forwards to middleware `POST /zapier/v1/timestamp`. +4. Middleware `checkEntitlement` (local plan limits today). +5. `veraeClient.createTimestamp`: + - **Mock:** in-process job, fake `jobId`. + - **Live:** `POST https://api.veraetime.net/api/timestamp` with JWT → **202** `{ jobId }`. +6. Splitter (in-process): hash vs attachments; optional `verae.archive.put` (bytes never go to chain). +7. `enqueueWatch` → JetStream **`verae.zapier.jobs.watch`**. +8. HTTP **202** `{ jobId, sha256, existing }` back to Zapier immediately. + +Same SHA-256 is idempotent (`existing: true`). + +## 2. Monitor (job poller) + +The poller is **in the middleware Node process** when `NATS_ENABLED=true` (`startJobPollerWorker`). Fleet does **not** spawn a second copy (`job-poller.enabled: false`). + +1. Durable consumer `job-poller` on `ZAPIER_JOBS` / `verae.zapier.jobs.watch`. +2. Resolve `tokenRef` → tenant Verae password → `POST` login → JWT (stays in middleware). +3. `GET /api/status/{jobId}` (mock or live timestamp server). +4. `pending` → `nak` after `intervalMs` (retry). +5. `completed` → publish `jobs.events` `{ event: timestamp.completed, status, jobId, tenantId, traceId }`. +6. `failed` → `timestamp.failed`. Too many attempts → `timestamp.timeout`. +7. `ack` the watch message. + +Live OpenAPI: poll until `completed` / `failed`. Hash lookup is **501** on live (`lookupHash`). Map `certificate`, `timestamp`, `blockIndex`, `sha256` into the receipt Zap samples expect. + +## 3. Wait (sync Zap step) + +**Create Timestamp and Wait** → `POST /zapier/v1/timestamp/wait`. + +1. Same as submit (watch is already on NATS). +2. `waitForJobEvent(jobId, WAIT_TIMEOUT_MS)` subscribes to **`verae.zapier.jobs.events`**. +3. On matching `jobId` event: return status JSON (certificate / receipt). +4. On timeout: `{ status: pending, jobId }` so the REST Hook can finish the Zap. + +`includeAttached`: after seal, aggregator should `verae.archive.query`; bloom hits merge extra receipts. **Simulator does this; live aggregator hook is TBD.** + +## 4. Push back to Zapier + +- **Wait response:** HTTP body on the same Zap step. +- **REST Hook:** Zapier subscribes `POST /zapier/v1/hooks`. On `jobs.events`, middleware enqueues **`verae.zapier.webhooks.deliver`**. Worker POSTs `targetUrl` (`hooks.zapier.com`). +- **Poll status:** Zap action **Find Job Status** → `GET /zapier/v1/status/{jobId}` → middleware `GET` Verae (or last event). + +## 5. Verify + +`POST /zapier/v1/verify` → middleware → Verae `POST /api/verify`. No job watch. + +## 6. Batch Merkle (designed; same bus) + +From `MESSAGE-FLOWS.md`: + +1. `POST /v1/timestamp/batch` — one item per line. +2. Middleware hashes leaves, builds Merkle tree. +3. Chain seals **only the root** (`POST /api/timestamp` on the root). +4. Each leaf proof → `verae.archive.put` `kind: tree`. +5. Central hash lookup of a leaf misses unless `includeTree=true`. +6. Aggregator broadcasts `verae.archive.query` `kinds=["tree"]`. +7. Tree node bloom hit → `archive.reply.` with proof. + +Live wait path does not yet call the aggregator, so tree-leaf receipts are not merged on NS1. + +## Connect live `api.veraetime.net` + +Still blocked on operator bind credentials. When ready: + +```bash +# middleware env +MOCK_VERAE=false +VERAE_API_BASE_URL=https://api.veraetime.net +NATS_ENABLED=true +NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +JETSTREAM_REPLICAS=3 +``` + +Then: working Verae login, one live seal, confirm 202/status/verify, map receipt fields, REST Hook to a private Zap. See `zapier-decisions/STATUS.md` “Once MOCK_VERAE=false”. + +## Billing on this path + +| Hop | Designed | Running | +|-----|----------|---------| +| Edge meter | `verae.billing.usage.recorded` | **coded**; fires when edge has `NATS_URL` | +| Account-balance | subscribe + debit prepaid; 402 if empty | **subscriber running**; 402 from books not proven on every Zap | +| Middleware quota | local files via `recordUsage` | **running**; not NATS | +| `verae.zapier.usage` | optional JS meter | stream only | + +## Connect a new internal process + +```bash +export NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +# clone verae-nats-process, rename subjects, register in verae-fleet +``` + +Jobs stay: HTTP in from Zapier → HTTPS to Verae → NATS watch/events → HTTP back to Zapier. diff --git a/04-gaps-and-finish.md b/04-gaps-and-finish.md new file mode 100644 index 0000000..4d3ad55 --- /dev/null +++ b/04-gaps-and-finish.md @@ -0,0 +1,81 @@ +# Gaps and finish list + +Modules and addresses that are designed but **not fully connected, not running as fleet workers, or not on the live Verae/Zapier path**. + +## Blocked on the operator (not a NATS bug) + +| Item | State | To finish | +|------|--------|-----------| +| Zapier Platform `push` | no login | `zapier login` → register → `MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org` → push private app | +| Live Verae | `MOCK_VERAE=true` | credentials; `MOCK_VERAE=false`; prove one seal | +| nkeys / mTLS | cluster `$G` only | `verae-nats-accounts` sketch; flip only after every `NATS_URL` has creds | +| Hash lookup on live OpenAPI | 501 | Verae API gap or our store | + +## Running on HTTP/NATS but incomplete routing + +| Module | Running? | NATS | Gap | +|--------|----------|------|-----| +| middleware HTTP + in-process job-poller + webhook-deliver | **yes** | watch/events/deliver | live Verae + Zapier push | +| account-balance | **yes** | billing RR + `usage.recorded` sub | prove 402 from books on a Zap when prepaid is 0 | +| zappier-edge | **yes** | `usage.recorded` / `payment.recorded` / `credit.applied` / `customer.put` **if `NATS_URL`** | confirm live process env; Stripe live | +| access-* + authz | **yes** | authz + ingress | — | +| identity | **yes** (fleet) | bind/lookup | confirm portal always binds `veraeUserId` | +| jobs-events mailbox `:3030` | **yes** (fleet) | events counter | exclusive consumer off unless `JOBS_EVENTS_EXCLUSIVE=1` | +| CS / sales / accounting | **yes** | statement/adjust | staff UX | +| archive-worm + tree-node | **yes on LXC 510** (keep) | `archive.put/query/reply` | only **1+1** copies; fleet overlay `enabled: false` for extra replicas | +| archive-aggregator | **no** as fleet worker | query/reply | enable overlay **or** finish in-process hook (`includeAttached` TBD in middleware) | +| request-splitter | library in middleware | `archive.put` | `verae.splitter.in` not used | +| `ZAPIER_USAGE` / `USAGE_WRITER` | stream yes | `verae.zapier.usage` | no consumer; middleware usage is local files | +| fleet job-poller / webhook-deliver **extra** copies | overlay **false** | same subjects | only enable if you want SSH-spread replicas **instead of** in-process (same queue names) | + +## Designed, not a live worker + +| Module | Address | What to do | +|--------|---------|------------| +| `verae-nats-process` | `verae.example.process.*` | template only; clone to add a function | +| `verae-activate` | none (HTTPS Zapier) | optional tiny app; needs Zapier login | +| `verae-zapier` (full app) | HTTPS | push after login | +| simulator | in-process fake NATS | local `:3847`; not production | +| `verae-nats-accounts` | nkeys policy | not flipped | + +## Aux (no product subjects, or HTTP only) + +| Module | NATS | Notes | +|--------|------|-------| +| `verae-keep` | none | HTTP health; restarts units | +| `verae-fleet` | none | catalog + keepFloor | +| `verae-uptime` | none | off-box HTTPS GET of public doors | +| `verae-backup` | none | disk / JetStream snapshot; no live event subject | +| `verae-bootstrap` / `verae-deploy` / `verae-proxmox` / `verae-ops` | none | install/link | +| staff-iam / staff-session / staff-ui | none | HTTP cookies | +| public NATS edge | leaf, token | **do not** use from Zapier; rotate token; later nkeys LEAF vs INTERNAL | + +## Billing triggers and event logging + +| Signal | Designed | Running | Finish | +|--------|----------|---------|--------| +| Timestamp/verify meter (edge) | `verae.billing.usage.recorded` | **code path exists** (`zappier/src/app.ts` `onUsage`) | Confirm `NATS_URL` on live edge; 402 from account-balance when prepaid empty | +| Middleware quota | local files `recordUsage` | **yes** | Either publish `usage.recorded` too or drop duplicate quota | +| Optional JS meter | `verae.zapier.usage` + `USAGE_WRITER` | stream only | Wire or delete | +| Portal reload | `payment.recorded` + `balance.adjust` | coded | confirm both fire on Stripe/test reload | +| CS credit | `balance.adjust` + `credit.applied` | coded | confirm applied pub on live | +| Customer upsert | `customer.put` | coded | — | +| Authz deny audit | `verae.access.authz.deny` | running on deny | **no SIEM sink** | +| Job lifecycle log | `jobs.events` | running | mailbox counts only — add structured log/export if needed | +| Archive put/query | `VERAE_ARCHIVE` | 510 worm/tree | aggregator not in wait path | +| Public edge | leaf | running | rotate token; do not expose `:6222` | + +## Ordered finish for message routing + +1. **Do not double-start** job-poller: either in-process **or** fleet copies, same queue `job-poller`. +2. Confirm edge **`NATS_URL`** so `usage.recorded` actually lands; then 402 from books. Unify or drop middleware local `recordUsage`. +3. Enable **archive-aggregator** (or in-process equivalent) so `includeAttached` and tree proofs work on wait/status. +4. Raise worm/tree **replica floors** on 510/more CTs if archive HA is required (fleet overlay min 3). +5. Operator: Zapier `push` + `MOCK_VERAE=false`. +6. Prove: Zap create → `jobs.watch` → live `GET /api/status` → `jobs.events` → wait body + REST Hook. +7. nkeys/mTLS after every client has creds; then lock down the public edge to accounts (LEAF vs INTERNAL). +8. Optional: exclusive `jobs-events` mailbox; SIEM on `authz.deny` and `jobs.events`; `verae.splitter.in` if splitter is split out; `USAGE_WRITER` or delete `ZAPIER_USAGE`. + +## What is already enough for a mock Zap + +HTTPS zap/mw → mock create → `jobs.watch` → in-process poller → mock status completed → `jobs.events` → wait returns mock certificate. Archive extras and live chain are the missing middle. diff --git a/05-developer-credentials.md b/05-developer-credentials.md new file mode 100644 index 0000000..916afea --- /dev/null +++ b/05-developer-credentials.md @@ -0,0 +1,271 @@ +# Developer credentials and connection info + +Lab pack for the Verae Time × Zapier stack on **NS1.GEORGELAMBERT.ORG** (`70.88.205.138`). Values below were checked **2026-09-14**. These are **lab defaults**, not production secrets. Rotate the public NATS token and admin secrets before treating this as a customer environment. + +Zapier cloud **never** gets a NATS URL or a Verae password. + +## 1. Who you are talking to + +| Host | Role | +|------|------| +| `70.88.205.138` | NS1 Proxmox. Public HTTPS + public NATS **client** edge | +| `10.10.10.20` | LXC 510 `verae-px-worker` (worm + tree) | +| `10.10.10.21–23` | LXC 511–513 `nats-a/b/c` JetStream cluster `verae` | + +SSH to the box: `ssh marchon@70.88.205.138` +Git SSH: `ssh://git@git.georgelambert.org:2223/marchon/.git` (port **2223**) + +## 2. Public HTTPS doors (live) + +Wildcard `*.zapier.georgelambert.org` → `70.88.205.138`. + +| URL | Process | Port | Last check | +|-----|---------|------|------------| +| https://zapier.georgelambert.org/ | docs catalog | files | 200 | +| https://git.georgelambert.org/ | Forgejo | :443 / SSH :2223 | 200 | +| https://portal.zapier.georgelambert.org/ | access-web → `/portal/` | :3021 | 302 → portal | +| https://api.zapier.georgelambert.org/health | access-api | :3022 | 200 | +| https://leaf.zapier.georgelambert.org/health | access-leaf | :3023 | 200 | +| https://zap.zapier.georgelambert.org/health | access-zapier | :3024 | 200 | +| https://staff.zapier.georgelambert.org/ | access-staff | :3025 | 302 login | +| https://login.zapier.georgelambert.org/ | staff-session | :3027 | 200 | +| https://iam.zapier.georgelambert.org/health | staff-iam | :3028 | 200 (`users: 5`) | +| https://cs.zapier.georgelambert.org/ | customer-service | :3011 | 302 | +| https://sales.zapier.georgelambert.org/ | sales-pricing | :3012 | 302 | +| https://acct.zapier.georgelambert.org/ | accounting-export | :3013 | 302 | +| https://mw.zapier.georgelambert.org/health | middleware | :3100 | 200 `mockVerae:true` `natsConnected:true` | +| https://fleet.zapier.georgelambert.org/ | fleet console | :3850 | 302 | +| https://nats.zapier.georgelambert.org/ | NATS WebSocket (Caddy) | 14422 | **400** on GET (expected; it is WS) | + +Loopback on NS1 (not public): zappier-edge **`127.0.0.1:13000`** (admin + Stripe + SQLite). Tunnel: + +```bash +ssh -L 13000:127.0.0.1:13000 marchon@70.88.205.138 +# then http://127.0.0.1:13000/admin and /docs +``` + +Operator console tunnel: `ssh -L 3850:127.0.0.1:3850 marchon@70.88.205.138` + +## 3. NATS + +### Internal cluster (workers, keep, fleet) + +No token. Private `vmbr1` only. + +```text +NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +JETSTREAM_REPLICAS=3 +``` + +| Port | Bind | Use | +|------|------|-----| +| `:4222` | `10.10.10.21–23` | clients | +| `:6222` | same, `vmbr1` | cluster routes — **do not publish** | +| `:7422` | same | leaf hubs for the public edge — **do not publish** | +| `:8222` | `127.0.0.1` on each LXC | monitor | + +From a laptop you **cannot** reach `10.10.10.x` unless you are on NS1 or you tunnel: + +```bash +ssh -L 14222:10.10.10.21:4222 -L 14223:10.10.10.22:4222 -L 14224:10.10.10.23:4222 marchon@70.88.205.138 +export NATS_URL=nats://127.0.0.1:14222,nats://127.0.0.1:14223,nats://127.0.0.1:14224 +``` + +### Public client edge (token) + +Live token (file `/etc/nats/public-edge.token` on NS1): + +```text +4743a49383ce2ce86e99d9d41c45c0004fe75eb23ef3a2b9 +``` + +```text +nats://4743a49383ce2ce86e99d9d41c45c0004fe75eb23ef3a2b9@70.88.205.138:4222 +wss://nats.zapier.georgelambert.org +``` + +`CONNECT` with that token returns `PONG` (checked 2026-09-14). `auth_required: true`. Server name `nats-public-edge`. **Do not** put this URL in Zapier. Recreate: `bash packages/verae-nats-cluster/scripts/expose-public.sh` on NS1. + +Leftover lab listeners (ignore for new clients): host `127.0.0.1:4222` docker-proxy, `10.10.10.1:4222` socat. zappier-edge process still has `NATS_URL=nats://127.0.0.1:4222` — billing pubs may miss the 3-node cluster until that env is flipped. + +nkeys/mTLS: **not** on. Isolation is LAN + token. + +## 4. Middleware (jobs) + +Live: `MOCK_VERAE=true`, `NATS_ENABLED=true`, cluster URL as above, `ADMIN_SECRET=change-me-admin` (default), `TOKEN_SECRET=dev-secret-change-me` (default). + +### Lab tenant (created 2026-09-14 for this pack) + +| Field | Value | +|-------|--------| +| tenantId | `tenant-dev-lab-local-89ab82de` | +| plan | free | +| API key | `zmw_pXdwlUUyMHkAvqj3RRoQ2gOG6yAUQlun` | +| mock Verae user | `lab-dev` / `lab-dev-pass` | +| veraeUserId | `vu_20ed6029b99efb2e` | + +Proven: `GET /zapier/v1/auth/me` → valid; `POST /zapier/v1/timestamp` → 202 `jobId`; wait → mock certificate `blockIndex: 42`. + +```bash +KEY=zmw_pXdwlUUyMHkAvqj3RRoQ2gOG6yAUQlun + +curl -sS -H "x-api-key: $KEY" https://mw.zapier.georgelambert.org/zapier/v1/auth/me + +curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \ + -d '{"sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \ + https://mw.zapier.georgelambert.org/zapier/v1/timestamp + +curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \ + -d '{"sha256":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}' \ + https://mw.zapier.georgelambert.org/zapier/v1/timestamp/wait +``` + +Customer API plane (forwards `x-api-key` to middleware): + +```bash +curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \ + -d '{"sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \ + https://api.zapier.georgelambert.org/v1/timestamp +``` + +`https://zap.zapier.georgelambert.org` is the Zapier **plane**. It does **not** currently forward `x-api-key` on timestamp (401 if you curl it with a key). Zapier Platform push is still waiting on login; when it is live, set `MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org` and use a `zmw_…` key in the Zap. + +Mint another key (mock Verae accepts any username/password): + +```bash +curl -sS -H "content-type: application/json" \ + -d '{"email":"you@lab.local","name":"You","veraeUsername":"you","veraePassword":"you-pass"}' \ + https://mw.zapier.georgelambert.org/zapier/v1/signup +``` + +Admin list/provision: + +```bash +curl -sS -H "x-admin-secret: change-me-admin" \ + https://mw.zapier.georgelambert.org/zapier/v1/admin/tenants +``` + +## 5. Portal / zappier-edge (billing users) + +SQLite `/SSD3/moved/home-marchon/verae-stack/packages/zappier/zappier.db`. Bind `127.0.0.1:13000`. + +Seed **customer API keys** (`x-api-key` on edge `/v1/*`, **not** middleware `zmw_`): + +| Customer | tier | key | +|----------|------|-----| +| Ada (free) | free | `key-ada` | +| Grace (pro) | pro | `key-grace` | +| Linus (business) | business | `key-linus` | +| Docs Capture | free | `key-021851970d8a22c6` | + +These keys are for the **edge meter / portal**. Middleware jobs need a `zmw_…` key (section 4). + +Seed **admin console** (`/admin`, `x-admin-key` or login): + +| username | password | +|----------|----------| +| `admin` | `admin-dev-key` | +| `demo` | `$$$Adm1n###` | + +`ZAPPIER_ADMIN_KEY` default on CS/sales/staff processes: `admin-dev-key`. + +Stripe: **not** live (lab quotes only). Env `STRIPE_*` unset on the sampled edge process. + +Signup more customers at https://portal.zapier.georgelambert.org/portal/ + +## 6. Staff IAM + +https://login.zapier.georgelambert.org/ · https://iam.zapier.georgelambert.org/ + +Seed users (defaults; live IAM has these five): + +| username | password | role | +|----------|----------|------| +| `admin` | `admin-dev-key` | owner | +| `cs` | `cs-dev-key` | cs | +| `sales` | `sales-dev-key` | sales | +| `accounting` | `acct-dev-key` | accounting | +| `operator` | `fleet-dev-key` | operator | + +`STAFF_KEY` / session fallback: `admin-dev-key`. + +## 7. Leaf plane + +https://leaf.zapier.georgelambert.org/health → `verae.access.leaf.in` + +Live: `LEAF_TOKEN=leaf-dev-token` + +```bash +curl -sS -H "content-type: application/json" \ + -d '{"token":"leaf-dev-token","target":"verae.archive.put","payload":{},"principal":"dev"}' \ + https://leaf.zapier.georgelambert.org/ +``` + +Billing targets are **denied** on this plane. + +## 8. Git (Forgejo) + +| | | +|--|--| +| Web | https://git.georgelambert.org/marchon/ | +| SSH | `ssh://git@git.georgelambert.org:2223/marchon/.git` | +| User | `marchon` | +| Overview of routes | https://git.georgelambert.org/marchon/dev-overview | +| Monorepo | `master-zapier-plan-draft` | +| Bootstrap | https://git.georgelambert.org/marchon/verae-bootstrap | + +```bash +GIT_SSH_COMMAND="ssh -p 2223" git clone ssh://git@git.georgelambert.org:2223/marchon/dev-overview.git +``` + +Add your SSH public key on Forgejo (user **marchon** or a new account). Do not commit deploy tokens. + +Clone-all: + +```bash +git clone ssh://git@git.georgelambert.org:2223/marchon/verae-bootstrap.git +cd verae-bootstrap +export VERAE_SRC=$HOME/verae-src +bash scripts/fetch.sh ns1-all-in-one +``` + +## 9. Environment cheat sheet (new worker on the cluster) + +```bash +export NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +export JETSTREAM_REPLICAS=3 +export NATS_ENABLED=true +export MOCK_VERAE=true # flip to false only with live Verae login +export VERAE_API_BASE_URL=https://api.veraetime.net +export AUTHZ_URL=http://127.0.0.1:3020 +export ZAPPIER_UPSTREAM=http://127.0.0.1:3100 +export ZAPPIER_ADMIN_URL=http://127.0.0.1:13000 +export ZAPPIER_ADMIN_KEY=admin-dev-key +export LEAF_TOKEN=leaf-dev-token +export STAFF_IAM_URL=http://127.0.0.1:3028 +``` + +Template worker: clone `verae-nats-process`, rename `verae.example.process.*`. + +## 10. Not issued yet (blocked) + +| Item | Why | +|------|-----| +| Zapier Platform deploy key / `zapier login` | operator has not logged the CLI in | +| Live `api.veraetime.net` username/password | `MOCK_VERAE=true`; JWT would stay in middleware | +| NATS nkeys / account creds | cluster is `$G` + LAN | +| Stripe live secret | lab only | +| lan-134 SSH worker | disabled | + +When Zapier login exists: `MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org`, auth field `api_key` = `zmw_…`. + +When live Verae exists: middleware `MOCK_VERAE=false` + `VERAE_API_BASE_URL=https://api.veraetime.net` + working login. Zapier still never sees that password. + +## 11. Related docs + +- Routes: [01-nats-routes.md](01-nats-routes.md) +- Endpoint I/O: [02-endpoint-io.md](02-endpoint-io.md) +- Job path: [03-zapier-verae-path.md](03-zapier-verae-path.md) +- Gaps: [04-gaps-and-finish.md](04-gaps-and-finish.md) +- Ops start: https://zapier.georgelambert.org/packages/verae-ops/GETTING-STARTED.pdf +- Status: https://zapier.georgelambert.org/packages/zapier-decisions/STATUS.pdf diff --git a/NATS.md b/NATS.md new file mode 100644 index 0000000..3af73fb --- /dev/null +++ b/NATS.md @@ -0,0 +1,9 @@ +# NATS — dev-overview + +This repo does not subscribe. It documents every `verae.*` address and the cluster TCP/leaf ports. + +Cluster (running): `nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222` on `vmbr1`. Cluster routes `:6222`. Leaf hubs `:7422`. Monitor `127.0.0.1:8222`. + +Public client edge (token): `nats://70.88.205.138:4222` and `wss://nats.zapier.georgelambert.org`. Zapier cloud still uses **HTTPS only**. + +See [01-nats-routes.md](01-nats-routes.md) and [02-endpoint-io.md](02-endpoint-io.md). diff --git a/README.html b/README.html new file mode 100644 index 0000000..eccb533 --- /dev/null +++ b/README.html @@ -0,0 +1,2518 @@ + + + + + + + dev-overview — NATS routes and developer credentials + + + +
+

dev-overview — NATS routes and developer +credentials

+
+

dev-overview

+

Developer map of the Verae Time × Zapier NATS message +fabric: what was designed, what is actually running on NS1, how +a Zapier timestamp job reaches api.veraetime.net (or the +mock) and comes back, and what is still unwired.

+

Forgejo: +https://git.georgelambert.org/marchon/dev-overview
+SSH: +ssh://git@git.georgelambert.org:2223/marchon/dev-overview.git
+Catalog: +https://zapier.georgelambert.org/packages/dev-overview/
+Date: 2026-09-14

+

Zapier cloud never connects to NATS. It talks HTTPS +to https://zap.zapier.georgelambert.org (access-zapier) +then middleware. api.veraetime.net is HTTPS +only — middleware is the NATS client.

+

Contents

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DocWhat
00-sources.mdEvery other doc that names a NATS address or TCP route
01-nats-routes.mdTransport ports + every verae.* address: designed vs +running
02-endpoint-io.mdPer-address: purpose, inbound from, outbound to, how to connect +Verae API
03-zapier-verae-path.mdZapier → middleware → Verae timestamp server → poll → events → +Zapier
04-gaps-and-finish.mdModules not connected; billing/event/aux; ordered finish list
05-developer-credentials.mdURLs, tokens, lab API keys, staff/IAM, git, env cheat sheet
+

Cluster (running)

+
nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+

JetStream streams (replicas=3, file): ZAPIER_JOBS, +ZAPIER_EVENTS, ZAPIER_WEBHOOKS, +ZAPIER_USAGE, VERAE_ARCHIVE.

+

Public token-gated client edge (not cluster routes): +nats://<token>@70.88.205.138:4222, +wss://nats.zapier.georgelambert.org. Do not put this in +Zapier.

+

Sources (other +documentation that names routes)

+

This inventory is compiled from code and from these docs. Do not +invent extra verae.* names.

+

Master maps

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PathWhat it names
packages/docs-master/modules-and-nats.mdModule catalog, subject table, HTTPS vs NATS
packages/docs-master/MESSAGE-FLOWS.mdCreate/wait/includeAttached/batch Merkle
packages/docs-master/archive-nats.mdArchive put/query/reply
docs/02-architecture/nats-subjects.mdverae.zapier.* payloads + streams
docs/02-architecture/modules-and-nats.mdSame catalog as docs-master
docs/02-architecture/overview.mdPoller / events / webhooks
packages/overview/03-nats-cluster.mdCluster URLs, who may connect
packages/overview/06-address-routing.mdAddress pattern + plane mapping
packages/verae-ops/06-linking-services.mdHow services attach
packages/verae-nats-cluster/scripts/ensure-streams.shLive stream create
packages/verae-nats-cluster/conf/nats.conf.tmpl:4222 / :6222 / :7422 / +monitor :8222
packages/verae-nats-cluster/conf/public-edge.conf.tmplPublic leaf :4222, WS :14422
packages/verae-fleet/overlays/ns1/fleet.jsonEnabled vs in-process workers
packages/verae-keep +units.px-worker.jsonWorm/tree on LXC 510
packages/verae-zapier-middleware/src/nats/subjects.jsProduct subjects + consumers
packages/zappier-account-balance/src/subjects.jsBilling subjects
packages/verae-access-authz/src/subjects.js + +policy.jsAuthz + plane allow lists
packages/zappier/src/billing-nats.tsEdge billing client
+

Per-package +NATS.md (Forgejo module copies also live under +docs-master/modules/)

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageAddresses
verae-zapier-middlewarejobs.watch out, jobs.events in, +archive.query via aggregator
verae-jobs-eventsjobs.events; exclusive → +verae.internal.jobs.events
verae-request-splitterarchive.put
verae-archive-wormarchive.put / query / +reply.<id>
verae-tree-nodesame, kind: tree
verae-archive-aggregatorquery out, reply.<id> in
zappier-account-balanceverae.billing.*
zappier (edge)package NATS.md is stale (“does not publish”); +code publishes usage/payment/credit/customer.put when +NATS_URL is set. Use +docs-master/modules/zappier/NATS.md
zappier-customer-servicestatement.get, balance.adjust
zappier-sales-pricingstatement.get
zappier-accounting-exportstatement.get
zappier-identityidentity.bind / lookup
verae-access-authzauthz.check / authz.deny
verae-access-webaccess.web.in / access.web.billing.*
verae-access-apiaccess.api.*
verae-access-zapieraccess.zapier.* then HTTPS to middleware
verae-access-leafaccess.leaf.in
verae-access-staffaccess.staff.*
verae-nats-processverae.example.process.* (template)
verae-nats-accountspolicy only (INTERNAL vs LEAF)
verae-zapier-simulatorin-process replay of product addresses
verae-keep / verae-fleet / +verae-uptime / verae-backupno product subjects
+

Simulator src/pipeline.js and +src/monitors.js list the same product addresses for traces; +they are not live subscribers.

+

Designed vs running NATS +routes

+

Designed = named in docs or in code.
+Running = a process on NS1 is subscribed or publishing +it today, and (for durable work) a JetStream stream +exists.

+

Cluster name verae. Internal clients use the three LXC +on vmbr1. Public edge is a leaf, not a +second cluster.

+

Legend: JS = JetStream stream; RR = +core request-reply; pub = core or JS fire-and-forget; +Q = queue group.

+

Transport (TCP / leaf / public)

+

These are NATS server routes, not +verae.* subjects.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EndpointDesignedRunningWho
10.10.10.21–23:4222 clientyesyesmiddleware, access-*, billing, worm/tree, identity, +CS/sales/acct
10.10.10.21–23:6222 cluster routesyesyes (private vmbr1 only)nats-a/b/c to each other
10.10.10.21–23:7422 leaf hubyes (public-edge)yespublic edge remotes
127.0.0.1:8222 monitor on each LXCyesyesoperators on the guest
70.88.205.138:4222 public clientyes (token)yesleaf edge; not Zapier
127.0.0.1:14422 WS on edgeyesyesCaddy → wss://nats.zapier.georgelambert.org
127.0.0.1:8229 edge monitoryesyeshost loopback
:6222 / :7422 / :8222 on +vmbr0nomust stay closed—
Host leftover 127.0.0.1:4222 (docker-proxy) + +10.10.10.1:4222 socatlab leftoverpresentdo not point product NATS_URL +here
MQTT :1883study onlyremoved—
+

Internal keep/fleet/middleware URL:

+
nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+

Public (token in /etc/nats/public-edge.token on NS1): +nats://<token>@70.88.205.138:4222. Isolation on the +hub is still $G + private LAN. nkeys/mTLS sketched in +verae-nats-accounts, not flipped.

+

JetStream streams (running)

+

Created by verae-nats-cluster/scripts/ensure-streams.sh +(middleware ensureStreams also creates the first +three).

+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StreamSubjectsReplicasStorageWho consumes
ZAPIER_JOBSverae.zapier.jobs.watch3filejob-poller (in middleware process), Q job-poller
ZAPIER_EVENTSverae.zapier.jobs.events3filemiddleware wait + webhook router +(event-webhook-router); optional exclusive +jobs-events
ZAPIER_WEBHOOKSverae.zapier.webhooks.deliver3filewebhook-deliver (in middleware), Q webhook-deliver
ZAPIER_USAGEverae.zapier.usage3fileno durable consumer (USAGE_WRITER +named only)
VERAE_ARCHIVEverae.archive.>3fileworm + tree on LXC 510 (keep); aggregator not +fleet-enabled
+

Billing is core request-reply / pub, not a JetStream +stream.

+

Address inventory

+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AddressKindDesignedRunningNotes
verae.zapier.jobs.watchJS work Q job-polleryesyesmiddleware publishes; in-process poller consumes
verae.zapier.jobs.eventsJS eventsyesyespoller publishes; wait() subscribes; webhook worker routes
verae.zapier.webhooks.deliverJS work Q webhook-deliveryesyessame Node process as HTTP
verae.zapier.usageJS optionalyesstream yes, consumer nomiddleware entitlement is local files
verae.internal.jobs.eventscore republishyes (jobs-events exclusive)partialonly if JOBS_EVENTS_EXCLUSIVE=1
verae.billing.statement.getRR Q account-balanceyesyesportal, CS, sales, accounting, access-web/api
verae.billing.balance.getRRyesyesaccount-balance
verae.billing.balance.adjustRRyesyesCS credits, portal reload
verae.billing.usage.recordedpubyesyes if edge NATS_URL setzappier/src/app.ts onUsage → +natsPublish; account-balance subscribes
verae.billing.payment.recordedpubyesyes if NATS_URLportal reload (portal.ts)
verae.billing.credit.appliedpubyesyes if NATS_URLadmin/CS (admin.ts)
verae.billing.customer.putpubyesyes if NATS_URLledger.ts; account-balance applies
verae.access.authz.checkRR Q access-authzyesyesfleet keepFloor
verae.access.authz.denypub audityesyes (on deny)no SIEM sink
verae.access.web.in / +verae.access.web.billing.*ingressyesyesmapped after authz, +e.g. verae.access.web.billing.statement.get
verae.access.api.*ingressyesyes
verae.access.zapier.*ingressyesyesHTTPS plane; Zapier never speaks NATS
verae.access.leaf.in / +verae.access.leaf.*ingressyesyesno billing
verae.access.staff.*ingressyesyesCS/sales/accounting
verae.identity.bindRR Q identityyesyesfleet
verae.identity.lookupRR Q identityyesyes
verae.archive.putJS (VERAE_ARCHIVE)yespartialsplitter in-process in middleware; worm/tree on 510 +consume
verae.archive.querybroadcast (no Q)yespartiallive aggregator hook TBD in middleware (simulator +has it)
verae.archive.reply.<id>replies on bloom hityespartialworm/tree on 510 can reply; aggregator not fleet-enabled
verae.splitter.infutureyes (docs)nosplitter stays a library
verae.example.process.in / .out / +.reply.*templateyesnoverae-nats-process is a model
bench.*lab onlyn/aephemeralspeed studies; not product
+

Plane mapping (verae-access-authz): internal +verae.billing.statement.get + plane web → +ingress verae.access.web.billing.statement.get. Planes +never publish internal subjects until authz.check returns +allow.

+

Durable consumers (named +in middleware)

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameStream / subjectRunning
job-pollerZAPIER_JOBS / jobs.watchyes (in-process)
event-webhook-routerZAPIER_EVENTS / jobs.eventsyes (in-process)
webhook-deliverZAPIER_WEBHOOKS / webhooks.deliveryes (in-process)
usage-writerZAPIER_USAGE / verae.zapier.usageno
jobs-events-exclusiveZAPIER_EVENTSno unless JOBS_EVENTS_EXCLUSIVE=1
+

Fleet overlay vs in-process

+

packages/verae-fleet/overlays/ns1/fleet.json +(2026-09-14):

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Servicefleet enabledActual lab
middleware-httptruerunning; starts job-poller + webhook workers +in-process when NATS_ENABLED
job-pollerfalsecovered by middleware; do not spawn a second copy
webhook-deliverfalsesame
archive-aggregatorfalsenot running as a fleet worker
archive-worm / tree-nodefalse on host fleetrunning on LXC 510 via keep +units.px-worker.json (1 worm + 1 tree)
account-balance, access-*, identity, jobs-events, CS/sales/acct, +staff-iam/session, zappier-edgetrue keepFloorkeepFloor
zapier-simulatortrue, keepFloor falseoptional
natsmanaged false3-node cluster + leftover host loopback
+

Endpoint I/O

+

Each product NATS address: what it is for, +who sends, who receives, how +to attach.

+

Connect with a long-lived client +(reconnect-per-message is the anti-pattern):

+
NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+

Public edge (token in /etc/nats/public-edge.token on +NS1): nats://<token>@70.88.205.138:4222. Do not put +that URL in Zapier or in Verae.

+
+

verae.zapier.jobs.watch

+ ++++ + + + + + + + + + + + + + + + + + + +
ForWork queue: “poll Verae until this jobId is +terminal.”
In frommiddleware enqueueWatch after +POST /api/timestamp (or mock) returns +jobId
Out tojob-poller worker (queue job-poller, durable on +ZAPIER_JOBS)
Thenpoller calls Verae GET /api/status/{jobId} over +HTTPS, not NATS
+

Payload: tenantId, veraeUserId, +jobId, tokenRef, enqueuedAt, +attempt, maxAttempts, intervalMs, +traceId. JWT stays in middleware (tokenRef); +never on Zapier.

+

Connect Verae-side: do not +subscribe this from api.veraetime.net. The poller is the +Verae HTTP client. To add another watcher, start another +job-poller consumer in the same queue group.

+
+

verae.zapier.jobs.events

+ ++++ + + + + + + + + + + + + + + +
ForTerminal outcomes: timestamp.completed / +failed / timeout
In fromjob-poller after Verae status is not pending
Out to(1) middleware waitForJobEvent (wait HTTP); (2) webhook +router → webhooks.deliver; (3) optional +verae-jobs-events mailbox :3030
+

If JOBS_EVENTS_EXCLUSIVE=1, only +verae-jobs-events is the durable consumer and republishes +verae.internal.jobs.events. Default lab: middleware wait + +webhook worker share the stream.

+

Payload: event, tenantId, +jobId, status, traceId, +emittedAt.

+
+

verae.zapier.webhooks.deliver

+ + + + + + + + + + + + + + + +
ForHTTPS POST to hooks.zapier.com (REST Hook)
In fromwebhook worker on completed/failed events
Out toZapier cloud HTTPS, never NATS
+

Payload: hookId, tenantId, +targetUrl, event, payload, +attempt, traceId. Slow consumer: keep replica +floors on the worker if you split it out of middleware.

+
+

verae.zapier.usage

+ ++++ + + + + + + + + + + + + + + + + + + +
ForOptional meter of Zap actions on NATS (JetStream)
In fromdesigned: middleware / edge
Out todesigned: USAGE_WRITER → books
Runningstream exists; no writer; middleware uses local usage +files
+

Prepaid meter that is wired uses +verae.billing.usage.recorded (core pub), not this stream. +Finish: either publish here from recordUsage or drop the +stream.

+
+

Billing +(verae.billing.*) — account-balance source of truth

+

Request-reply unless noted. Queue account-balance. +Not JetStream.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AddressForIn fromOut to
statement.getprepaid, credits, usage, paymentsportal, CS, sales, accounting, access-web/apireply to requester
balance.getcents onlyinternalreply
balance.adjustCS credit or portal reloadCS, access-web (reload kind only)books + reply
usage.recordedmeter pubzappier-edge meter() onUsage when +NATS_URL setaccount-balance subscriber (debits prepaid)
payment.recordedreload/invoice pubportalbooks
credit.appliedgoodwill pubCS/adminbooks
customer.putupsert customeredge ledgerbooks
+

Authz must pass +verae.access.authz.check before these from a public plane. +Leaf plane cannot call billing (policy).

+

Middleware recordUsage still writes local tenant +files and does not publish +usage.recorded. Two meters until that is unified.

+
+

Access planes

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AddressForInOut
verae.access.authz.checkallow/deny { plane, subject, principal }every plane + edge billing-nats.ts{ allow, reason }; deny also pubs +authz.deny
verae.access.authz.denyauditauthz on denynowhere yet (no SIEM)
verae.access.web.in / +verae.access.web.billing.*browser portalaccess-web HTTPstatement.get / balance.adjust kind=reload +after authz
verae.access.api.*customer x-api-keyaccess-apistatement; HTTPS to middleware for jobs
verae.access.zapier.*Zapier HTTPS onlyaccess-zapierjobs.watch via HTTP to middleware, not Zapier NATS; +usage.recorded allowed by policy
verae.access.leaf.inS2S { token, target, payload }remote leafarchive + jobs after authz; never billing
verae.access.staff.*CS/sales/accountingaccess-staffbilling after authz
+

Ingress mapping: +accessAddress(plane, 'verae.billing.statement.get') → +verae.access.<plane>.billing.statement.get.

+
+

Identity

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + +
AddressForInOut
verae.identity.bindbind email/customerId → veraeUserIdportal / middlewarereply
verae.identity.lookuplookup by customerId or veraeUserIdsamereply
+

Queue identity. Fleet keepFloor.

+
+

Archive / tree

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AddressForInOut
verae.archive.putWORM/tree write (kind: publicMeta / privateMeta / file +/ tree)splitter (in-process from middleware today)every worm/tree (JS)
verae.archive.querybroadcast lookupaggregatorall worm/tree, no queue group
verae.archive.reply.<correlationId>bloom hit onlyworm/treeaggregator
+

Silence = miss. Chain seal does not wait on archive. Bytes never go +to the chain.

+
+

Template / unused

+ +++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AddressForInOutRunning
verae.splitter.infuture split-out splitter processmiddlewaresplitterno
verae.example.process.intemplate workeranyexample-process Qno
verae.example.process.outtemplate fan-outtemplatepeersno
verae.example.process.reply.<id>template replytemplaterequesterno
verae.internal.jobs.eventsexclusive mailbox republishjobs-eventswait/webhookno unless env
+
+

How to connect Verae +API to NATS

+

api.veraetime.net is HTTPS. It does not +subscribe. The glue is middleware:

+
    +
  1. Set VERAE_API_BASE_URL=https://api.veraetime.net and +MOCK_VERAE=false.
  2. +
  3. Middleware login (username/password) → JWT stored as +tokenRef.
  4. +
  5. POST /api/timestamp → 202 { jobId }.
  6. +
  7. Publish verae.zapier.jobs.watch.
  8. +
  9. Poller GET /api/status/{jobId} until +completed / failed.
  10. +
  11. Publish verae.zapier.jobs.events.
  12. +
  13. Wait HTTP and/or REST Hook.
  14. +
+

Do not point Verae at NATS_URL. Do +not put NATS in Zapier env. A new worker that must see +seals uses jobs.events or HTTP +GET /zapier/v1/status/{jobId}.

+

Template for a new addressed process: clone +verae-nats-process, rename +verae.example.process.*, add the subject to +verae-access-authz policy for the plane that may call +it.

+

Zapier → Verae API → Zapier

+

How a timestamp job is submitted, watched, and returned. Lab today: +MOCK_VERAE=true. Live path is the same HTTP contract with +MOCK_VERAE=false.

+

Rule

+
Zapier cloud  --HTTPS only-->  zap.zapier.georgelambert.org  (access-zapier)
+                                    |
+                                    v HTTPS
+                              middleware :3100 /zapier/v1
+                                    |
+                    +---------------+----------------+
+                    | HTTPS                         | NATS (private cluster)
+                    v                               v
+            api.veraetime.net              jobs.watch → poller
+            POST /api/timestamp            GET /api/status/{jobId}
+            GET  /api/status/{jobId}              |
+            POST /api/verify                      v
+                                           jobs.events
+                                              |
+                         +--------------------+--------------------+
+                         | wait subscriber                         | webhook-deliver
+                         v                                         v
+                    HTTP response to Zap                    POST hooks.zapier.com
+

Zapier never has NATS_URL. Verae never has +NATS_URL.

+

1. Submit (async create)

+
    +
  1. Zap action Create Timestamp (or curl) +POST https://zap.zapier.georgelambert.org/zapier/v1/timestamp +with portal API key zmw_… (not a Verae JWT).
  2. +
  3. access-zapier / edge: authz, then meter() records +cents; if NATS_URL is set, +natsPublish(verae.billing.usage.recorded).
  4. +
  5. Forwards to middleware POST /zapier/v1/timestamp.
  6. +
  7. Middleware checkEntitlement (local plan limits +today).
  8. +
  9. veraeClient.createTimestamp: +
      +
    • Mock: in-process job, fake jobId.
    • +
    • Live: +POST https://api.veraetime.net/api/timestamp with JWT → +202 { jobId }.
    • +
  10. +
  11. Splitter (in-process): hash vs attachments; optional +verae.archive.put (bytes never go to chain).
  12. +
  13. enqueueWatch → JetStream +verae.zapier.jobs.watch.
  14. +
  15. HTTP 202 { jobId, sha256, existing } +back to Zapier immediately.
  16. +
+

Same SHA-256 is idempotent (existing: true).

+

2. Monitor (job poller)

+

The poller is in the middleware Node process when +NATS_ENABLED=true (startJobPollerWorker). +Fleet does not spawn a second copy +(job-poller.enabled: false).

+
    +
  1. Durable consumer job-poller on ZAPIER_JOBS +/ verae.zapier.jobs.watch.
  2. +
  3. Resolve tokenRef → tenant Verae password → +POST login → JWT (stays in middleware).
  4. +
  5. GET /api/status/{jobId} (mock or live timestamp +server).
  6. +
  7. pending → nak after +intervalMs (retry).
  8. +
  9. completed → publish jobs.events +{ event: timestamp.completed, status, jobId, tenantId, traceId }.
  10. +
  11. failed → timestamp.failed. Too many +attempts → timestamp.timeout.
  12. +
  13. ack the watch message.
  14. +
+

Live OpenAPI: poll until completed / +failed. Hash lookup is 501 on live +(lookupHash). Map certificate, +timestamp, blockIndex, sha256 +into the receipt Zap samples expect.

+

3. Wait (sync Zap step)

+

Create Timestamp and Wait → +POST /zapier/v1/timestamp/wait.

+
    +
  1. Same as submit (watch is already on NATS).
  2. +
  3. waitForJobEvent(jobId, WAIT_TIMEOUT_MS) subscribes to +verae.zapier.jobs.events.
  4. +
  5. On matching jobId event: return status JSON +(certificate / receipt).
  6. +
  7. On timeout: { status: pending, jobId } so the REST Hook +can finish the Zap.
  8. +
+

includeAttached: after seal, aggregator should +verae.archive.query; bloom hits merge extra receipts. +Simulator does this; live aggregator hook is TBD.

+

4. Push back to Zapier

+
    +
  • Wait response: HTTP body on the same Zap step.
  • +
  • REST Hook: Zapier subscribes +POST /zapier/v1/hooks. On jobs.events, +middleware enqueues +verae.zapier.webhooks.deliver. Worker +POSTs targetUrl (hooks.zapier.com).
  • +
  • Poll status: Zap action Find Job +Status → GET /zapier/v1/status/{jobId} → +middleware GET Verae (or last event).
  • +
+

5. Verify

+

POST /zapier/v1/verify → middleware → Verae +POST /api/verify. No job watch.

+

6. Batch Merkle (designed; same +bus)

+

From MESSAGE-FLOWS.md:

+
    +
  1. POST /v1/timestamp/batch — one item per line.
  2. +
  3. Middleware hashes leaves, builds Merkle tree.
  4. +
  5. Chain seals only the root +(POST /api/timestamp on the root).
  6. +
  7. Each leaf proof → verae.archive.put +kind: tree.
  8. +
  9. Central hash lookup of a leaf misses unless +includeTree=true.
  10. +
  11. Aggregator broadcasts verae.archive.query +kinds=["tree"].
  12. +
  13. Tree node bloom hit → archive.reply.<id> with +proof.
  14. +
+

Live wait path does not yet call the aggregator, so tree-leaf +receipts are not merged on NS1.

+

Connect live +api.veraetime.net

+

Still blocked on operator bind credentials. When ready:

+
# middleware env
+MOCK_VERAE=false
+VERAE_API_BASE_URL=https://api.veraetime.net
+NATS_ENABLED=true
+NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+JETSTREAM_REPLICAS=3
+

Then: working Verae login, one live seal, confirm 202/status/verify, +map receipt fields, REST Hook to a private Zap. See +zapier-decisions/STATUS.md “Once MOCK_VERAE=false”.

+

Billing on this path

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HopDesignedRunning
Edge meterverae.billing.usage.recordedcoded; fires when edge has +NATS_URL
Account-balancesubscribe + debit prepaid; 402 if emptysubscriber running; 402 from books not proven on +every Zap
Middleware quotalocal files via recordUsagerunning; not NATS
verae.zapier.usageoptional JS meterstream only
+

Connect a new internal +process

+
export NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+# clone verae-nats-process, rename subjects, register in verae-fleet
+

Jobs stay: HTTP in from Zapier → HTTPS to Verae → NATS watch/events → +HTTP back to Zapier.

+

Gaps and finish list

+

Modules and addresses that are designed but not fully +connected, not running as fleet workers, or not on the live Verae/Zapier +path.

+

Blocked on the operator +(not a NATS bug)

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ItemStateTo finish
Zapier Platform pushno loginzapier login → register → +MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org → +push private app
Live VeraeMOCK_VERAE=truecredentials; MOCK_VERAE=false; prove one seal
nkeys / mTLScluster $G onlyverae-nats-accounts sketch; flip only after every +NATS_URL has creds
Hash lookup on live OpenAPI501Verae API gap or our store
+

Running on HTTP/NATS +but incomplete routing

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModuleRunning?NATSGap
middleware HTTP + in-process job-poller + webhook-deliveryeswatch/events/deliverlive Verae + Zapier push
account-balanceyesbilling RR + usage.recorded subprove 402 from books on a Zap when prepaid is 0
zappier-edgeyesusage.recorded / payment.recorded / +credit.applied / customer.put if +NATS_URLconfirm live process env; Stripe live
access-* + authzyesauthz + ingress—
identityyes (fleet)bind/lookupconfirm portal always binds veraeUserId
jobs-events mailbox :3030yes (fleet)events counterexclusive consumer off unless +JOBS_EVENTS_EXCLUSIVE=1
CS / sales / accountingyesstatement/adjuststaff UX
archive-worm + tree-nodeyes on LXC 510 (keep)archive.put/query/replyonly 1+1 copies; fleet overlay +enabled: false for extra replicas
archive-aggregatorno as fleet workerquery/replyenable overlay or finish in-process hook +(includeAttached TBD in middleware)
request-splitterlibrary in middlewarearchive.putverae.splitter.in not used
ZAPIER_USAGE / USAGE_WRITERstream yesverae.zapier.usageno consumer; middleware usage is local files
fleet job-poller / webhook-deliver extra +copiesoverlay falsesame subjectsonly enable if you want SSH-spread replicas instead +of in-process (same queue names)
+

Designed, not a live worker

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModuleAddressWhat to do
verae-nats-processverae.example.process.*template only; clone to add a function
verae-activatenone (HTTPS Zapier)optional tiny app; needs Zapier login
verae-zapier (full app)HTTPSpush after login
simulatorin-process fake NATSlocal :3847; not production
verae-nats-accountsnkeys policynot flipped
+

Aux (no product subjects, +or HTTP only)

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModuleNATSNotes
verae-keepnoneHTTP health; restarts units
verae-fleetnonecatalog + keepFloor
verae-uptimenoneoff-box HTTPS GET of public doors
verae-backupnonedisk / JetStream snapshot; no live event subject
verae-bootstrap / verae-deploy / +verae-proxmox / verae-opsnoneinstall/link
staff-iam / staff-session / staff-uinoneHTTP cookies
public NATS edgeleaf, tokendo not use from Zapier; rotate token; later nkeys +LEAF vs INTERNAL
+

Billing triggers and event +logging

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SignalDesignedRunningFinish
Timestamp/verify meter (edge)verae.billing.usage.recordedcode path exists (zappier/src/app.ts +onUsage)Confirm NATS_URL on live edge; 402 from account-balance +when prepaid empty
Middleware quotalocal files recordUsageyesEither publish usage.recorded too or drop duplicate +quota
Optional JS meterverae.zapier.usage + USAGE_WRITERstream onlyWire or delete
Portal reloadpayment.recorded + balance.adjustcodedconfirm both fire on Stripe/test reload
CS creditbalance.adjust + credit.appliedcodedconfirm applied pub on live
Customer upsertcustomer.putcoded—
Authz deny auditverae.access.authz.denyrunning on denyno SIEM sink
Job lifecycle logjobs.eventsrunningmailbox counts only — add structured log/export if needed
Archive put/queryVERAE_ARCHIVE510 worm/treeaggregator not in wait path
Public edgeleafrunningrotate token; do not expose :6222
+

Ordered finish for message +routing

+
    +
  1. Do not double-start job-poller: either in-process +or fleet copies, same queue +job-poller.
  2. +
  3. Confirm edge NATS_URL so +usage.recorded actually lands; then 402 from books. Unify +or drop middleware local recordUsage.
  4. +
  5. Enable archive-aggregator (or in-process +equivalent) so includeAttached and tree proofs work on +wait/status.
  6. +
  7. Raise worm/tree replica floors on 510/more CTs if +archive HA is required (fleet overlay min 3).
  8. +
  9. Operator: Zapier push + +MOCK_VERAE=false.
  10. +
  11. Prove: Zap create → jobs.watch → live +GET /api/status → jobs.events → wait body + +REST Hook.
  12. +
  13. nkeys/mTLS after every client has creds; then lock down the public +edge to accounts (LEAF vs INTERNAL).
  14. +
  15. Optional: exclusive jobs-events mailbox; SIEM on +authz.deny and jobs.events; +verae.splitter.in if splitter is split out; +USAGE_WRITER or delete ZAPIER_USAGE.
  16. +
+

What is already enough +for a mock Zap

+

HTTPS zap/mw → mock create → jobs.watch → in-process +poller → mock status completed → jobs.events → wait returns +mock certificate. Archive extras and live chain are the missing +middle.

+

Developer credentials +and connection info

+

Lab pack for the Verae Time × Zapier stack on +NS1.GEORGELAMBERT.ORG (70.88.205.138). +Values below were checked 2026-09-14. These are +lab defaults, not production secrets. Rotate the public +NATS token and admin secrets before treating this as a customer +environment.

+

Zapier cloud never gets a NATS URL or a Verae +password.

+

1. Who you are talking to

+ ++++ + + + + + + + + + + + + + + + + + + + + +
HostRole
70.88.205.138NS1 Proxmox. Public HTTPS + public NATS client +edge
10.10.10.20LXC 510 verae-px-worker (worm + tree)
10.10.10.21–23LXC 511–513 nats-a/b/c JetStream cluster +verae
+

SSH to the box: ssh marchon@70.88.205.138
+Git SSH: +ssh://git@git.georgelambert.org:2223/marchon/<repo>.git +(port 2223)

+

2. Public HTTPS doors (live)

+

Wildcard *.zapier.georgelambert.org → +70.88.205.138.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
URLProcessPortLast check
https://zapier.georgelambert.org/docs catalogfiles200
https://git.georgelambert.org/Forgejo:443 / SSH :2223200
https://portal.zapier.georgelambert.org/access-web → /portal/:3021302 → portal
https://api.zapier.georgelambert.org/healthaccess-api:3022200
https://leaf.zapier.georgelambert.org/healthaccess-leaf:3023200
https://zap.zapier.georgelambert.org/healthaccess-zapier:3024200
https://staff.zapier.georgelambert.org/access-staff:3025302 login
https://login.zapier.georgelambert.org/staff-session:3027200
https://iam.zapier.georgelambert.org/healthstaff-iam:3028200 (users: 5)
https://cs.zapier.georgelambert.org/customer-service:3011302
https://sales.zapier.georgelambert.org/sales-pricing:3012302
https://acct.zapier.georgelambert.org/accounting-export:3013302
https://mw.zapier.georgelambert.org/healthmiddleware:3100200 mockVerae:true natsConnected:true
https://fleet.zapier.georgelambert.org/fleet console:3850302
https://nats.zapier.georgelambert.org/NATS WebSocket (Caddy)14422400 on GET (expected; it is WS)
+

Loopback on NS1 (not public): zappier-edge +127.0.0.1:13000 (admin + Stripe + SQLite). +Tunnel:

+
ssh -L 13000:127.0.0.1:13000 marchon@70.88.205.138
+# then http://127.0.0.1:13000/admin  and  /docs
+

Operator console tunnel: +ssh -L 3850:127.0.0.1:3850 marchon@70.88.205.138

+

3. NATS

+

Internal cluster (workers, +keep, fleet)

+

No token. Private vmbr1 only.

+
NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+JETSTREAM_REPLICAS=3
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PortBindUse
:422210.10.10.21–23clients
:6222same, vmbr1cluster routes — do not publish
:7422sameleaf hubs for the public edge — do not publish
:8222127.0.0.1 on each LXCmonitor
+

From a laptop you cannot reach +10.10.10.x unless you are on NS1 or you tunnel:

+
ssh -L 14222:10.10.10.21:4222 -L 14223:10.10.10.22:4222 -L 14224:10.10.10.23:4222 marchon@70.88.205.138
+export NATS_URL=nats://127.0.0.1:14222,nats://127.0.0.1:14223,nats://127.0.0.1:14224
+

Public client edge (token)

+

Live token (file /etc/nats/public-edge.token on +NS1):

+
4743a49383ce2ce86e99d9d41c45c0004fe75eb23ef3a2b9
+
nats://4743a49383ce2ce86e99d9d41c45c0004fe75eb23ef3a2b9@70.88.205.138:4222
+wss://nats.zapier.georgelambert.org
+

CONNECT with that token returns PONG +(checked 2026-09-14). auth_required: true. Server name +nats-public-edge. Do not put this URL in +Zapier. Recreate: +bash packages/verae-nats-cluster/scripts/expose-public.sh +on NS1.

+

Leftover lab listeners (ignore for new clients): host +127.0.0.1:4222 docker-proxy, 10.10.10.1:4222 +socat. zappier-edge process still has +NATS_URL=nats://127.0.0.1:4222 — billing pubs may miss the +3-node cluster until that env is flipped.

+

nkeys/mTLS: not on. Isolation is LAN + token.

+

4. Middleware (jobs)

+

Live: MOCK_VERAE=true, NATS_ENABLED=true, +cluster URL as above, ADMIN_SECRET=change-me-admin +(default), TOKEN_SECRET=dev-secret-change-me (default).

+

Lab tenant (created +2026-09-14 for this pack)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldValue
tenantIdtenant-dev-lab-local-89ab82de
planfree
API keyzmw_pXdwlUUyMHkAvqj3RRoQ2gOG6yAUQlun
mock Verae userlab-dev / lab-dev-pass
veraeUserIdvu_20ed6029b99efb2e
+

Proven: GET /zapier/v1/auth/me → valid; +POST /zapier/v1/timestamp → 202 jobId; wait → +mock certificate blockIndex: 42.

+
KEY=zmw_pXdwlUUyMHkAvqj3RRoQ2gOG6yAUQlun
+
+curl -sS -H "x-api-key: $KEY" https://mw.zapier.georgelambert.org/zapier/v1/auth/me
+
+curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \
+  -d '{"sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
+  https://mw.zapier.georgelambert.org/zapier/v1/timestamp
+
+curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \
+  -d '{"sha256":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}' \
+  https://mw.zapier.georgelambert.org/zapier/v1/timestamp/wait
+

Customer API plane (forwards x-api-key to +middleware):

+
curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \
+  -d '{"sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
+  https://api.zapier.georgelambert.org/v1/timestamp
+

https://zap.zapier.georgelambert.org is the Zapier +plane. It does not currently forward +x-api-key on timestamp (401 if you curl it with a key). +Zapier Platform push is still waiting on login; when it is live, set +MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org +and use a zmw_… key in the Zap.

+

Mint another key (mock Verae accepts any username/password):

+
curl -sS -H "content-type: application/json" \
+  -d '{"email":"you@lab.local","name":"You","veraeUsername":"you","veraePassword":"you-pass"}' \
+  https://mw.zapier.georgelambert.org/zapier/v1/signup
+

Admin list/provision:

+
curl -sS -H "x-admin-secret: change-me-admin" \
+  https://mw.zapier.georgelambert.org/zapier/v1/admin/tenants
+

5. Portal / zappier-edge +(billing users)

+

SQLite +/SSD3/moved/home-marchon/verae-stack/packages/zappier/zappier.db. +Bind 127.0.0.1:13000.

+

Seed customer API keys (x-api-key on +edge /v1/*, not middleware +zmw_):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Customertierkey
Ada (free)freekey-ada
Grace (pro)prokey-grace
Linus (business)businesskey-linus
Docs Capturefreekey-021851970d8a22c6
+

These keys are for the edge meter / portal. +Middleware jobs need a zmw_… key (section 4).

+

Seed admin console (/admin, +x-admin-key or login):

+ + + + + + + + + + + + + + + + + +
usernamepassword
adminadmin-dev-key
demo$$$Adm1n###
+

ZAPPIER_ADMIN_KEY default on CS/sales/staff processes: +admin-dev-key.

+

Stripe: not live (lab quotes only). Env +STRIPE_* unset on the sampled edge process.

+

Signup more customers at +https://portal.zapier.georgelambert.org/portal/

+

6. Staff IAM

+

https://login.zapier.georgelambert.org/ · +https://iam.zapier.georgelambert.org/

+

Seed users (defaults; live IAM has these five):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
usernamepasswordrole
adminadmin-dev-keyowner
cscs-dev-keycs
salessales-dev-keysales
accountingacct-dev-keyaccounting
operatorfleet-dev-keyoperator
+

STAFF_KEY / session fallback: +admin-dev-key.

+

7. Leaf plane

+

https://leaf.zapier.georgelambert.org/health → +verae.access.leaf.in

+

Live: LEAF_TOKEN=leaf-dev-token

+
curl -sS -H "content-type: application/json" \
+  -d '{"token":"leaf-dev-token","target":"verae.archive.put","payload":{},"principal":"dev"}' \
+  https://leaf.zapier.georgelambert.org/
+

Billing targets are denied on this plane.

+

8. Git (Forgejo)

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Webhttps://git.georgelambert.org/marchon/
SSHssh://git@git.georgelambert.org:2223/marchon/<name>.git
Usermarchon
Overview of routeshttps://git.georgelambert.org/marchon/dev-overview
Monorepomaster-zapier-plan-draft
Bootstraphttps://git.georgelambert.org/marchon/verae-bootstrap
+
GIT_SSH_COMMAND="ssh -p 2223" git clone ssh://git@git.georgelambert.org:2223/marchon/dev-overview.git
+

Add your SSH public key on Forgejo (user marchon or +a new account). Do not commit deploy tokens.

+

Clone-all:

+
git clone ssh://git@git.georgelambert.org:2223/marchon/verae-bootstrap.git
+cd verae-bootstrap
+export VERAE_SRC=$HOME/verae-src
+bash scripts/fetch.sh ns1-all-in-one
+

9. +Environment cheat sheet (new worker on the cluster)

+
export NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
+export JETSTREAM_REPLICAS=3
+export NATS_ENABLED=true
+export MOCK_VERAE=true          # flip to false only with live Verae login
+export VERAE_API_BASE_URL=https://api.veraetime.net
+export AUTHZ_URL=http://127.0.0.1:3020
+export ZAPPIER_UPSTREAM=http://127.0.0.1:3100
+export ZAPPIER_ADMIN_URL=http://127.0.0.1:13000
+export ZAPPIER_ADMIN_KEY=admin-dev-key
+export LEAF_TOKEN=leaf-dev-token
+export STAFF_IAM_URL=http://127.0.0.1:3028
+

Template worker: clone verae-nats-process, rename +verae.example.process.*.

+

10. Not issued yet (blocked)

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ItemWhy
Zapier Platform deploy key / zapier loginoperator has not logged the CLI in
Live api.veraetime.net username/passwordMOCK_VERAE=true; JWT would stay in middleware
NATS nkeys / account credscluster is $G + LAN
Stripe live secretlab only
lan-134 SSH workerdisabled
+

When Zapier login exists: +MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org, +auth field api_key = zmw_….

+

When live Verae exists: middleware MOCK_VERAE=false + +VERAE_API_BASE_URL=https://api.veraetime.net + working +login. Zapier still never sees that password.

+ + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4be046f --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# dev-overview + +Developer map of the Verae Time × Zapier **NATS message fabric**: what was designed, what is actually running on NS1, how a Zapier timestamp job reaches `api.veraetime.net` (or the mock) and comes back, and what is still unwired. + +**Forgejo:** https://git.georgelambert.org/marchon/dev-overview +**SSH:** `ssh://git@git.georgelambert.org:2223/marchon/dev-overview.git` +**Catalog:** https://zapier.georgelambert.org/packages/dev-overview/ +**Date:** 2026-09-14 + +Zapier cloud **never** connects to NATS. It talks HTTPS to `https://zap.zapier.georgelambert.org` (access-zapier) then middleware. `api.veraetime.net` is **HTTPS only** — middleware is the NATS client. + +## Contents + +| Doc | What | +|-----|------| +| [00-sources.md](00-sources.md) | Every other doc that names a NATS address or TCP route | +| [01-nats-routes.md](01-nats-routes.md) | Transport ports + every `verae.*` address: designed vs running | +| [02-endpoint-io.md](02-endpoint-io.md) | Per-address: purpose, inbound from, outbound to, how to connect Verae API | +| [03-zapier-verae-path.md](03-zapier-verae-path.md) | Zapier → middleware → Verae timestamp server → poll → events → Zapier | +| [04-gaps-and-finish.md](04-gaps-and-finish.md) | Modules not connected; billing/event/aux; ordered finish list | +| [05-developer-credentials.md](05-developer-credentials.md) | URLs, tokens, lab API keys, staff/IAM, git, env cheat sheet | + +## Cluster (running) + +```text +nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222 +``` + +JetStream streams (replicas=3, file): `ZAPIER_JOBS`, `ZAPIER_EVENTS`, `ZAPIER_WEBHOOKS`, `ZAPIER_USAGE`, `VERAE_ARCHIVE`. + +Public token-gated **client** edge (not cluster routes): `nats://@70.88.205.138:4222`, `wss://nats.zapier.georgelambert.org`. Do not put this in Zapier. diff --git a/README.pdf b/README.pdf new file mode 100644 index 0000000..53d9222 Binary files /dev/null and b/README.pdf differ diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..15ad1b3 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,8 @@ +# dev-overview + +Developer map of **designed vs running** NATS addresses, transport ports, per-endpoint I/O, the Zapier → Verae timestamp path, and what is still unconnected. + +**Forgejo:** https://git.georgelambert.org/marchon/dev-overview +**Catalog:** https://zapier.georgelambert.org/packages/dev-overview/ + +Start: [README.md](README.md). Credentials: [05-developer-credentials.md](05-developer-credentials.md). diff --git a/package.json b/package.json new file mode 100644 index 0000000..50cd11b --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "dev-overview", + "version": "0.1.0", + "private": true, + "description": "Developer map of designed vs running NATS routes, Zapier↔Verae job path, and remaining wiring" +}