Wire zappier-edge into the live stack and add billing department APIs
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
Fleet now spawns the real zappier and middleware processes. Metered timestamp/receipt/hash calls proxy to middleware when ZAPPIER_UPSTREAM is set. CS credits, sales per-customer pricing, and QuickBooks export are separate repos plugged into zappier-edge admin.
This commit is contained in:
parent
65bfa544b2
commit
a1a5b957fd
44 changed files with 822 additions and 18 deletions
|
|
@ -5,7 +5,8 @@ Users → Zapier UI
|
|||
Zapier cloud runs packages/verae-zapier
|
||||
--HTTPS, x-api-key--> packages/zappier /v1/*
|
||||
(meter, quote, 401/403, usage)
|
||||
--internal HTTPS--> packages/verae-zapier-middleware /zapier/v1/*
|
||||
--ZAPPIER_UPSTREAM--> packages/verae-zapier-middleware /zapier/v1/*
|
||||
CS :3011, Sales :3012, Accounting/QuickBooks :3013 --> zappier-edge admin
|
||||
--sync--> api.veraetime.net or MOCK_VERAE
|
||||
--NATS--> workers --> Verae + Zapier REST Hooks
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# zappier-accounting-export NATS
|
||||
|
||||
None.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# zappier-accounting-export
|
||||
|
||||
Exports zappier invoices to QuickBooks IIF and accounting CSV.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# zappier-customer-service NATS
|
||||
|
||||
None. HTTPS only to zappier-edge admin.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# zappier-customer-service
|
||||
|
||||
CS department API for prepaid credit additions. Writes `balanceCents` on zappier-edge.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# zappier-sales-pricing NATS
|
||||
|
||||
None.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# zappier-sales-pricing
|
||||
|
||||
Sales department per-customer tier and multiplier. Writes through zappier-edge `/admin/api/customers`.
|
||||
|
|
@ -12,6 +12,7 @@ Verae Time proves a SHA-256 existed at a given time. Zapier lets customers regis
|
|||
|-------|------|---------|
|
||||
| Zapier cloud | `verae-zapier-app`, `verae-activate` | HTTPS to zappier-edge |
|
||||
| Commercial edge | `zappier-edge` portal, admin, `x-api-key`, Stripe meter | Public HTTPS |
|
||||
| CS / sales / accounting | `zappier-customer-service`, `zappier-sales-pricing`, `zappier-accounting-export` | Loopback/private HTTP into zappier-edge admin |
|
||||
| Middleware HTTP | `/zapier/v1/*` job id + wait | Public HTTPS from edge only |
|
||||
| NATS cluster | JetStream subjects under `verae.*` | Private; loopback or SSH tunnel |
|
||||
| Workers | poller, webhook-deliver, aggregator | NATS + HTTPS to chain or Zapier hooks |
|
||||
|
|
@ -21,6 +22,6 @@ Verae Time proves a SHA-256 existed at a given time. Zapier lets customers regis
|
|||
|
||||
## Request in one sentence
|
||||
|
||||
A Zap step POSTs to zappier-edge; edge meters and forwards to middleware; middleware splits hash vs files, writes the hash (or Merkle root) to the chain, publishes `verae.zapier.jobs.watch`, and either returns `jobId` or waits on `verae.zapier.jobs.events`. Attached metadata and bulk-summary **leaves** are found later by broadcasting `verae.archive.query` to every archive/tree node.
|
||||
A Zap step POSTs to zappier-edge (`x-api-key`); edge meters the call and, when `ZAPPIER_UPSTREAM` is set, forwards timestamp/receipt/hash to middleware `/zapier/v1`. Middleware splits hash vs files, writes the hash (or Merkle root) to the chain, publishes `verae.zapier.jobs.watch`, and either returns `jobId` or waits on `verae.zapier.jobs.events`. Customer-service credits, sales per-customer multipliers, invoices, payment history, and QuickBooks export sit on zappier-edge (department APIs on :3011–:3013). Attached metadata and bulk-summary **leaves** are found later by broadcasting `verae.archive.query` to every archive/tree node.
|
||||
|
||||
See [diagrams](08-diagrams.md).
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ Each runtime piece is its **own git repo** on Forgejo (`git.georgelambert.org`,
|
|||
| **overview** | `packages/overview` | This high-level map |
|
||||
| **verae-nats-process** | `packages/verae-nats-process` | **Template** for a new addressed NATS process |
|
||||
| **master-zapier-plan-draft** | workspace root | Combined snapshot (`main` and `master`) |
|
||||
| **zappier-edge** | `packages/zappier` | Metered HTTPS, portal, admin, Stripe `zappier.api_cents` |
|
||||
| **zappier-edge** | `packages/zappier` | Metered HTTPS, portal, admin, Stripe; **proxies Verae calls to middleware** |
|
||||
| **zappier-customer-service** | `packages/zappier-customer-service` | CS goodwill credits onto prepaid balances |
|
||||
| **zappier-sales-pricing** | `packages/zappier-sales-pricing` | Sales per-customer tier / multiplier |
|
||||
| **zappier-accounting-export** | `packages/zappier-accounting-export` | QuickBooks IIF + accounting CSV |
|
||||
| **verae-middleware** | `packages/verae-zapier-middleware` | `/zapier/v1`, job wait, NATS publishers/workers |
|
||||
| **verae-zapier-app** | `packages/verae-zapier` | Full Zapier nouns (timestamp, wait, batch, hash + tree lookup) |
|
||||
| **verae-activate** | `packages/verae-activate` | Activate-now (Add Numbers, Echo, SHA256, mock timestamp) |
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ SVG files in [`diagrams/`](diagrams/). The same shapes are repeated below in mer
|
|||
```mermaid
|
||||
flowchart LR
|
||||
Z[Zapier cloud apps] -->|HTTPS x-api-key| E[zappier-edge]
|
||||
E -->|HTTPS| M[verae-middleware]
|
||||
CS[customer-service] --> E
|
||||
SA[sales-pricing] --> E
|
||||
AC[accounting-export QuickBooks] --> E
|
||||
E -->|HTTPS metered| M[verae-middleware]
|
||||
M -->|HTTPS| C[Verae chain]
|
||||
M -->|JetStream| N[NATS 3-node cluster]
|
||||
N --> P[job-poller]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
<rect x="200" y="80" width="140" height="56" rx="8" fill="#0f6e56"/>
|
||||
<text x="270" y="104" fill="#fff">zappier-edge</text>
|
||||
<text x="270" y="120" fill="#d4efe6" font-size="10">HTTPS :3000 meter</text>
|
||||
<rect x="175" y="148" width="62" height="36" rx="6" fill="#fff" stroke="#0f6e56"/>
|
||||
<text x="206" y="170" fill="#12202c" font-size="8">CS credits</text>
|
||||
<rect x="242" y="148" width="62" height="36" rx="6" fill="#fff" stroke="#0f6e56"/>
|
||||
<text x="273" y="170" fill="#12202c" font-size="8">Sales $</text>
|
||||
<rect x="309" y="148" width="62" height="36" rx="6" fill="#fff" stroke="#0f6e56"/>
|
||||
<text x="340" y="170" fill="#12202c" font-size="8">QBooks</text>
|
||||
<rect x="380" y="80" width="160" height="56" rx="8" fill="#0b4f8a"/>
|
||||
<text x="460" y="104" fill="#fff">verae-middleware</text>
|
||||
<text x="460" y="120" fill="#d9e2e8" font-size="10">HTTPS :3100 wait</text>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 4 KiB |
|
|
@ -15,6 +15,9 @@
|
|||
"services": {
|
||||
"nats": { "min": 1, "max": 1, "keepFloor": false, "enabled": true, "managed": false },
|
||||
"zappier-edge": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"customer-service": { "min": 0, "max": 1, "keepFloor": false, "enabled": true },
|
||||
"sales-pricing": { "min": 0, "max": 1, "keepFloor": false, "enabled": true },
|
||||
"accounting-export": { "min": 0, "max": 1, "keepFloor": false, "enabled": true },
|
||||
"middleware-http": { "min": 1, "max": 1, "keepFloor": true, "enabled": true },
|
||||
"job-poller": { "min": 1, "max": 2, "keepFloor": true, "enabled": true },
|
||||
"webhook-deliver": { "min": 1, "max": 2, "keepFloor": true, "enabled": true },
|
||||
|
|
|
|||
21
packages/verae-fleet/services/accounting-export.json
Normal file
21
packages/verae-fleet/services/accounting-export.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"id": "accounting-export",
|
||||
"title": "Accounting export (QuickBooks)",
|
||||
"kind": "http",
|
||||
"package": "zappier-accounting-export",
|
||||
"role": "accounting-export",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3013 IIF/CSV → zappier-edge invoices",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3013 },
|
||||
"spawn": {
|
||||
"cwd": "../zappier-accounting-export",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3013",
|
||||
"ZAPPIER_ADMIN_URL": "http://127.0.0.1:3000"
|
||||
},
|
||||
"nats": { "in": [], "out": [] }
|
||||
}
|
||||
22
packages/verae-fleet/services/customer-service.json
Normal file
22
packages/verae-fleet/services/customer-service.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"id": "customer-service",
|
||||
"title": "Customer-service credits",
|
||||
"kind": "http",
|
||||
"package": "zappier-customer-service",
|
||||
"role": "customer-service",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3011 CS credits → zappier-edge",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3011 },
|
||||
"spawn": {
|
||||
"cwd": "../zappier-customer-service",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3011",
|
||||
"ZAPPIER_ADMIN_URL": "http://127.0.0.1:3000"
|
||||
},
|
||||
"nats": { "in": [], "out": [] },
|
||||
"notes": "Department API. Prepaid balances live on zappier-edge."
|
||||
}
|
||||
|
|
@ -6,9 +6,19 @@
|
|||
"role": "middleware-http",
|
||||
"managed": true,
|
||||
"runtime": "HTTPS :3100 /zapier/v1",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 800 },
|
||||
"ports": { "healthBase": 13100 },
|
||||
"env": { "PORT": "3100", "NATS_URL": "nats://127.0.0.1:4222" },
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 4000 },
|
||||
"ports": { "healthBase": 3100 },
|
||||
"spawn": {
|
||||
"cwd": "../verae-zapier-middleware",
|
||||
"command": "node",
|
||||
"args": ["src/index.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3100",
|
||||
"NATS_URL": "nats://127.0.0.1:4222",
|
||||
"NATS_ENABLED": "true",
|
||||
"MOCK_VERAE": "true"
|
||||
},
|
||||
"nats": {
|
||||
"in": ["verae.zapier.jobs.events"],
|
||||
"out": ["verae.zapier.jobs.watch", "verae.zapier.webhooks.deliver"]
|
||||
|
|
|
|||
21
packages/verae-fleet/services/sales-pricing.json
Normal file
21
packages/verae-fleet/services/sales-pricing.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"id": "sales-pricing",
|
||||
"title": "Sales per-customer pricing",
|
||||
"kind": "http",
|
||||
"package": "zappier-sales-pricing",
|
||||
"role": "sales-pricing",
|
||||
"managed": true,
|
||||
"runtime": "HTTP :3012 quotes → zappier-edge",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 2000 },
|
||||
"ports": { "healthBase": 3012 },
|
||||
"spawn": {
|
||||
"cwd": "../zappier-sales-pricing",
|
||||
"command": "node",
|
||||
"args": ["src/server.js"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3012",
|
||||
"ZAPPIER_ADMIN_URL": "http://127.0.0.1:3000"
|
||||
},
|
||||
"nats": { "in": [], "out": [] }
|
||||
}
|
||||
|
|
@ -5,10 +5,21 @@
|
|||
"package": "zappier",
|
||||
"role": "zappier-edge",
|
||||
"managed": true,
|
||||
"runtime": "HTTPS :3000 (portal, admin, meter)",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 800 },
|
||||
"ports": { "healthBase": 13000 },
|
||||
"env": { "PORT": "3000" },
|
||||
"runtime": "HTTPS :3000 (portal, admin, meter, proxy to middleware)",
|
||||
"health": { "type": "http", "path": "/health", "timeoutMs": 4000 },
|
||||
"ports": { "healthBase": 3000 },
|
||||
"spawn": {
|
||||
"cwd": "../zappier",
|
||||
"command": "npx",
|
||||
"args": ["ts-node", "--transpile-only", "src/index.ts"]
|
||||
},
|
||||
"env": {
|
||||
"PORT": "3000",
|
||||
"ZAPPIER_UPSTREAM": "http://127.0.0.1:3100",
|
||||
"CS_SERVICE_URL": "http://127.0.0.1:3011",
|
||||
"SALES_PRICING_URL": "http://127.0.0.1:3012",
|
||||
"ACCOUNTING_EXPORT_URL": "http://127.0.0.1:3013"
|
||||
},
|
||||
"nats": { "in": [], "out": [] },
|
||||
"notes": "Zapier x-api-key lands here. HTTPS to middleware only."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,23 @@ import { sshSpawnWorker, sshKillWorker, sshHttp, sshCheck } from './ssh.js';
|
|||
|
||||
const WORKER = path.join(path.dirname(fileURLToPath(import.meta.url)), 'worker.js');
|
||||
|
||||
/**
|
||||
* Real HTTP apps (zappier-edge, middleware) set `spawn` in services/*.json.
|
||||
* Everything else is the fleet health worker.
|
||||
*/
|
||||
export function spawnLocal(root, spec, env, healthPort) {
|
||||
const merged = { ...env, ...(spec.env || {}), PORT: String(healthPort) };
|
||||
if (spec.spawn?.command) {
|
||||
const cwd = path.resolve(root, spec.spawn.cwd || '.');
|
||||
return spawn(spec.spawn.command, spec.spawn.args || [], {
|
||||
env: merged,
|
||||
cwd,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
}
|
||||
return spawn(process.execPath, [WORKER], { env: merged, stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
}
|
||||
|
||||
export class Supervisor {
|
||||
/**
|
||||
* @param {object} [opts]
|
||||
|
|
@ -184,7 +201,7 @@ export class Supervisor {
|
|||
return null;
|
||||
}
|
||||
} else {
|
||||
child = spawn(process.execPath, [WORKER], { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
child = spawnLocal(this.loaded.root, spec, env, healthPort);
|
||||
pid = child.pid;
|
||||
}
|
||||
const rec = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { loadFleet, listServices, FLEET_ROOT } from '../src/load.js';
|
||||
import { Supervisor } from '../src/supervisor.js';
|
||||
import { Supervisor, spawnLocal } from '../src/supervisor.js';
|
||||
import { Monitor } from '../src/monitor.js';
|
||||
|
||||
function treeOnly(healthBase) {
|
||||
|
|
@ -32,6 +32,20 @@ describe('verae-fleet', () => {
|
|||
for (const s of supervisors) await s.stopAll();
|
||||
});
|
||||
|
||||
it('spawnLocal runs spawn.command instead of the stub worker', async () => {
|
||||
const child = spawnLocal(
|
||||
FLEET_ROOT,
|
||||
{ spawn: { command: process.execPath, args: ['-e', 'process.stdout.write("ok"); process.exit(0)'] } },
|
||||
{ ...process.env },
|
||||
9,
|
||||
);
|
||||
const out = [];
|
||||
child.stdout.on('data', (c) => out.push(c));
|
||||
const code = await new Promise((resolve) => child.on('exit', resolve));
|
||||
assert.equal(code, 0);
|
||||
assert.equal(Buffer.concat(out).toString(), 'ok');
|
||||
});
|
||||
|
||||
it('lists every service with a config file and central replica spec', () => {
|
||||
const loaded = loadFleet();
|
||||
const rows = listServices(loaded);
|
||||
|
|
@ -39,6 +53,9 @@ describe('verae-fleet', () => {
|
|||
for (const need of [
|
||||
'nats',
|
||||
'zappier-edge',
|
||||
'customer-service',
|
||||
'sales-pricing',
|
||||
'accounting-export',
|
||||
'middleware-http',
|
||||
'job-poller',
|
||||
'webhook-deliver',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ This is the **top document** for install, first-up, and monitoring. Platform-spe
|
|||
|
||||
| Piece | Job | Default |
|
||||
|-------|-----|---------|
|
||||
| **zappier-edge** | Billing, API keys, customer portal, admin, Stripe | `:3000` public HTTPS |
|
||||
| **zappier-edge** | Billing, API keys, customer portal, admin, Stripe; meters then **proxies** to middleware | `:3000` public HTTPS |
|
||||
| **customer-service** | CS credit additions | `:3011` |
|
||||
| **sales-pricing** | Per-customer sales quotes | `:3012` |
|
||||
| **accounting-export** | QuickBooks IIF / CSV | `:3013` |
|
||||
| **verae-middleware** | Zapier `/zapier/v1`, chain, NATS | `:3100` |
|
||||
| **NATS JetStream** | Private jobs / archive | `:4222` **not public** |
|
||||
| **worm ×3, tree-node ×3** | Off-chain files and Merkle leaves | fleet floor |
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue