master-zapier-plan-draft/packages/dev-overview/02-endpoint-io.md
George Lambert 86b421db05
Some checks failed
offline / test (push) Has been cancelled
Add dev-overview: designed vs running NATS routes and Zapier-Verae path.
Independent Forgejo repo maps every verae.* address, endpoint I/O, job
monitor loop, and remaining billing/archive wiring.
2026-09-14 13:25:04 -04:00

135 lines
5.3 KiB
Markdown

# Endpoint I/O
Each product NATS address: **what it is for**, **who sends**, **who receives**, **how to attach**.
Connect with a **long-lived** client:
```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.
---
## `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 |
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.
---
## `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 |
Slow consumer: this is the flood/backlog path. Keep replica floors on the worker.
---
## `verae.zapier.usage`
| | |
|--|--|
| For | Optional meter of Zap actions on NATS |
| In from | designed: middleware / edge |
| Out to | designed: `USAGE_WRITER` → books |
| Running | **stream exists; no writer; middleware uses local usage files** |
Finish: publish here from `recordUsage`, consume into `verae.billing.usage.recorded`.
---
## Billing (`verae.billing.*`) — account-balance SoT
Request-reply unless noted. Queue on account-balance. **Not** JetStream (except you could mirror later).
| Address | For | In from | Out to |
|---------|-----|---------|--------|
| `statement.get` | prepaid, credits, usage, payments | portal, CS, sales, admin, 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 (designed) | account-balance subscriber |
| `payment.recorded` | reload/invoice pub | portal | books |
| `credit.applied` | goodwill pub | CS | books |
| `customer.put` | upsert customer | edge/IAM | books |
Authz **must** pass `verae.access.authz.check` before these from a public plane. Leaf plane **cannot** call billing (policy).
---
## Access planes
| Address | For | In | Out |
|---------|-----|----|-----|
| `verae.access.authz.check` | allow/deny `{ plane, subject, principal }` | every plane | `{ allow, reason }`; deny also pubs `authz.deny` |
| `verae.access.web.*` | browser portal | access-web HTTP | billing statement/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 |
| `verae.access.leaf.*` | S2S | access-leaf | archive + jobs; **never billing** |
| `verae.access.staff.*` | CS/sales/accounting | access-staff | billing after authz |
---
## 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 (hash, meta, file meta, merkle leaf) | 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.
---
## 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.*`.