Add developer credentials: URLs, lab keys, NATS token, staff IAM, git

This commit is contained in:
George Lambert 2026-09-14 13:50:45 -04:00
commit f850002f1c
12 changed files with 3364 additions and 0 deletions

55
00-sources.md Normal file
View file

@ -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.<id>` |
| `verae-tree-node` | same, `kind: tree` |
| `verae-archive-aggregator` | `query` out, `reply.<id>` 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.

106
01-nats-routes.md Normal file
View file

@ -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://<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).
| 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.<id>` | 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 |

154
02-endpoint-io.md Normal file
View file

@ -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://<token>@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.<plane>.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.<correlationId>` | 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.<id>` | 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.

125
03-zapier-verae-path.md Normal file
View file

@ -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.<id>` 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.

81
04-gaps-and-finish.md Normal file
View file

@ -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.

271
05-developer-credentials.md Normal file
View file

@ -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/<repo>.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/<name>.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

9
NATS.md Normal file
View file

@ -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).

2518
README.html Normal file

File diff suppressed because it is too large Load diff

31
README.md Normal file
View file

@ -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://<token>@70.88.205.138:4222`, `wss://nats.zapier.georgelambert.org`. Do not put this in Zapier.

BIN
README.pdf Normal file

Binary file not shown.

8
SUMMARY.md Normal file
View file

@ -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).

6
package.json Normal file
View file

@ -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"
}