diff --git a/README.MD b/README.MD index 74ef63d..ec8abdb 100644 --- a/README.MD +++ b/README.MD @@ -13,6 +13,8 @@ They are **imported, not yet wired**. Public Zapier traffic should eventually hi Original brief (features a–m): [docs/00-sources/workspace-brief.md](docs/00-sources/workspace-brief.md). Where each tree came from: [docs/00-sources/provenance.md](docs/00-sources/provenance.md). +**Tomorrow morning:** [docs/04-activate/SETUP-ZAPIER-DEVELOPER.md](docs/04-activate/SETUP-ZAPIER-DEVELOPER.md) — create a Zapier developer account and push **Add Numbers**. + ## Quick start ```bash @@ -36,6 +38,10 @@ Gates (`npm run gate:N`) continue the middleware plan in [TODO.md](TODO.md). Pha | [docs/02-architecture/overview.md](docs/02-architecture/overview.md) | Adapter internals (imported) | | [docs/03-zapier/operations.md](docs/03-zapier/operations.md) | Platform nouns | | [research/README.md](research/README.md) | Imported Zapier vendor research | +| [docs/04-activate/SETUP-ZAPIER-DEVELOPER.md](docs/04-activate/SETUP-ZAPIER-DEVELOPER.md) | **Morning: Zapier developer account + push Add Numbers** | +| [docs/modules/README.md](docs/modules/README.md) | Per-module API sheets (params, callees, returns) | +| [docs/models/README.md](docs/models/README.md) | Data models | +| [docs/sphinx/_build/html/index.html](docs/sphinx/_build/html/index.html) | Sphinx HTML (rebuild: `npm run docs:sphinx`) | ## Environment (middleware) diff --git a/TODO.md b/TODO.md index bd35abb..2d96480 100644 --- a/TODO.md +++ b/TODO.md @@ -378,10 +378,10 @@ npm run gate:8 ### Tasks -- [ ] 9.1 API waits on job event (subscription or inbox) after enqueue watch -- [ ] 9.2 Hard timeout returns `{ jobId, status: "pending" }` (document Zapier fallback) -- [ ] 9.3 Tests: fast complete; timeout path; cancellation on client disconnect (if feasible) -- [ ] 9.4 Docs update: wait semantics, Zapier multi-step fallback +- [x] 9.1 API waits on job event (subscription or inbox) after enqueue watch +- [x] 9.2 Hard timeout returns `{ jobId, status: "pending" }` (document Zapier fallback) +- [x] 9.3 Tests: fast complete; timeout path; cancellation on client disconnect (if feasible) +- [x] 9.4 Docs update: wait semantics, Zapier multi-step fallback ### GATE 9 — Wait path @@ -399,7 +399,7 @@ npm run gate:9 | Date | Result | Notes | |------|--------|-------| -| | | | +| 2026-09-09 | pass | wait via NATS + timeout pending; NS1 tunnel 70.88.205.138 | --- @@ -480,9 +480,9 @@ npm run gate:11 ### Tasks -- [ ] 12.1 `docker-compose.yml`: middleware + NATS (+ optional mock Verae) -- [ ] 12.2 Compose smoke script: health, auth, timestamp wait, webhook -- [ ] 12.3 Debug mode runbook: reproduce a failed job with `DEBUG_VERAE=*` +- [x] 12.1 `docker-compose.yml`: middleware + NATS (+ optional mock Verae) +- [x] 12.2 Compose smoke script: health, auth, timestamp wait, webhook +- [x] 12.3 Debug mode runbook: reproduce a failed job with `DEBUG_VERAE=*` - [ ] 12.4 Record sample traces in `docs/developer/debugging.md` ### GATE 12 — E2E local @@ -502,7 +502,7 @@ npm run gate:12 | Date | Result | Notes | |------|--------|-------| -| | | | +| 2026-09-09 | pass | harness/scripts/smoke.sh health + signup + wait | --- diff --git a/docs/01-product/features-a-m.md b/docs/01-product/features-a-m.md index 3220301..05e9603 100644 --- a/docs/01-product/features-a-m.md +++ b/docs/01-product/features-a-m.md @@ -6,8 +6,8 @@ Source: [workspace-brief.md](../00-sources/workspace-brief.md). Owners after com |----|------------|-------|-------------------------|--------------------| | a | Customer gets own API key | zappier `/portal` signup + regen | Built (`x-api-key`) | No | | b | Billing after usage limit | zappier meter + credit + portal reload + Stripe + admin PO | Built | No | -| c | Register SHA256 or return original timestamp + ref | verae middleware | Partial: `POST /zapier/v1/timestamp` → `jobId`. No idempotent hash key | `POST /api/timestamp` (`data`, `hashAlg`) → 202 `jobId` | -| d | Lookup by SHA256 | verae mock (later) | Missing | No (verify is certificate; status is jobId) | +| c | Register SHA256 or return original timestamp + ref | verae middleware mock + zappier `/v1/timestamp` | **Mock done** — second create returns original `jobId` + `existing: true` | Live OpenAPI still `data` only | +| d | Lookup by SHA256 | `GET /zapier/v1/hashes/{sha256}` and zappier `/v1/hashes/{sha256}` | **Mock done** | No live Verae route | | e | Timestamp + public/private metadata | verae mock | Missing | No | | f | e + encrypted LTS binary | verae mock | Missing. Do not use zappier demo `/v1/storage` | No | | g | Share file/dir + decryption key | verae mock | Missing | No | diff --git a/docs/02-architecture/nats-gateway.md b/docs/02-architecture/nats-gateway.md new file mode 100644 index 0000000..a843cd2 --- /dev/null +++ b/docs/02-architecture/nats-gateway.md @@ -0,0 +1,21 @@ +# NATS gateway + +JetStream for job watch, events, and webhook delivery lives on **NS1** (`70.88.205.138`, host `NS1.GEORGELAMBERT.ORG`). + +The client port is **not public**. `nats-server -js` listens on `127.0.0.1:4222` (monitoring `8222`) with JetStream enabled (NATS 2.11.17). + +## Tunnel from this Mac + +```bash +ssh -fN -L 14222:127.0.0.1:4222 marchon@70.88.205.138 +export NATS_URL=nats://127.0.0.1:14222 +export NATS_ENABLED=true +``` + +Local isolated tests use a laptop `nats-server -js -p 4222`. + +## Streams created by middleware + +`ZAPIER_JOBS`, `ZAPIER_EVENTS`, `ZAPIER_WEBHOOKS` (optional `ZAPIER_USAGE`). + +Do not expose 4222 on `0.0.0.0` without auth; keep SSH tunnel or private network. diff --git a/docs/04-activate/SETUP-ZAPIER-DEVELOPER.md b/docs/04-activate/SETUP-ZAPIER-DEVELOPER.md index a7edee0..da6d7a9 100644 --- a/docs/04-activate/SETUP-ZAPIER-DEVELOPER.md +++ b/docs/04-activate/SETUP-ZAPIER-DEVELOPER.md @@ -103,7 +103,7 @@ If `register` says the name is taken, use `"Verae Time Dev"` or `"VeraeTime"`. - **API Key** — leave **blank**. - **API base URL** — leave **blank**. - Test connection → should succeed (*Verae Time (Add Numbers)* / local mode). -5. Choose action **Add Numbers**. +5. Choose action **Add Numbers** (or **Echo Text**). 6. Set **Number 1** = `2`, **Number 2** = `3`. 7. **Test step**. You want: diff --git a/docs/WORK-LOG.md b/docs/WORK-LOG.md index 8cac6ac..1bd532d 100644 --- a/docs/WORK-LOG.md +++ b/docs/WORK-LOG.md @@ -11,7 +11,13 @@ Running record of milestones. Ordered by dependency. | M4 | Sphinx + PDF for every module + 10 models | M3 | done | | M5 | Git checkin docs + lessons | M4 | this commit | | M6 | Integration hardening (`/v1/add` on zappier, Add Numbers local) | M2 | done for activate-now | -| M7 | Ready-to-activate final checkin | M5 | this commit | +| M7 | Ready-to-activate final checkin | M5 | done `10c663c` | +| M8 | Phase 9 wait-via-NATS + tests | M7 | done | +| M9 | Phase 12 smoke | M8 | done | +| M10 | Hash idempotent register/lookup mock | M7 | done | +| M11 | Zappier /v1/timestamp + hash-lookup | M10 | done | +| M12 | NS1 NATS 70.88.205.138 via SSH tunnel | — | done | +| M13 | Echo action on activate app | M7 | done | ## Learned (M1) diff --git a/docs/models-pdf/AddNumbersResult.pdf b/docs/models-pdf/AddNumbersResult.pdf index cdc3fe8..cf017ef 100644 --- a/docs/models-pdf/AddNumbersResult.pdf +++ b/docs/models-pdf/AddNumbersResult.pdf @@ -37,7 +37,7 @@ endobj endobj 7 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -68,7 +68,7 @@ xref trailer << /ID -[] +[<2f955ee0641a9f5c09cc35b2e18cf0bf><2f955ee0641a9f5c09cc35b2e18cf0bf>] % ReportLab generated PDF document -- digest (opensource) /Info 7 0 R diff --git a/docs/models-pdf/CallUsage.pdf b/docs/models-pdf/CallUsage.pdf index e6220b4..bc30b61 100644 --- a/docs/models-pdf/CallUsage.pdf +++ b/docs/models-pdf/CallUsage.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[] +[<9cae5edd3d4e0bcf06baee891a5999ff><9cae5edd3d4e0bcf06baee891a5999ff>] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/Customer.pdf b/docs/models-pdf/Customer.pdf index 1cc844b..0ce2bb5 100644 --- a/docs/models-pdf/Customer.pdf +++ b/docs/models-pdf/Customer.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[] +[] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/PriceRule.pdf b/docs/models-pdf/PriceRule.pdf index 2d3d6a9..2e11bc8 100644 --- a/docs/models-pdf/PriceRule.pdf +++ b/docs/models-pdf/PriceRule.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[<08dab07d9f5bd9cd434cb151f6b84b52><08dab07d9f5bd9cd434cb151f6b84b52>] +[<19922f9449900f729a32c70fe4a164c4><19922f9449900f729a32c70fe4a164c4>] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/Quote.pdf b/docs/models-pdf/Quote.pdf index f0fbb52..1c7c64c 100644 --- a/docs/models-pdf/Quote.pdf +++ b/docs/models-pdf/Quote.pdf @@ -37,7 +37,7 @@ endobj endobj 7 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -68,7 +68,7 @@ xref trailer << /ID -[<73bc4dd8168535f5f72a61a3ae8226cb><73bc4dd8168535f5f72a61a3ae8226cb>] +[<1be202b4fec6e54a0454781367cd42da><1be202b4fec6e54a0454781367cd42da>] % ReportLab generated PDF document -- digest (opensource) /Info 7 0 R diff --git a/docs/models-pdf/RateCard.pdf b/docs/models-pdf/RateCard.pdf index 180be1b..8926d2e 100644 --- a/docs/models-pdf/RateCard.pdf +++ b/docs/models-pdf/RateCard.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[] +[] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/StatusResponse.pdf b/docs/models-pdf/StatusResponse.pdf index 7284ea1..dd61669 100644 --- a/docs/models-pdf/StatusResponse.pdf +++ b/docs/models-pdf/StatusResponse.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[<8ee03c12859ca5bbdda2373d43becfb5><8ee03c12859ca5bbdda2373d43becfb5>] +[<30f5c8829cce51759bfaf173130abd2c><30f5c8829cce51759bfaf173130abd2c>] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/TierConfig.pdf b/docs/models-pdf/TierConfig.pdf index b484f11..6d8db00 100644 --- a/docs/models-pdf/TierConfig.pdf +++ b/docs/models-pdf/TierConfig.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[<7bcbdd69c5f87d3b17d28804fb2d17ec><7bcbdd69c5f87d3b17d28804fb2d17ec>] +[] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/TimestampRequest.pdf b/docs/models-pdf/TimestampRequest.pdf index 18b2a68..4dc8580 100644 --- a/docs/models-pdf/TimestampRequest.pdf +++ b/docs/models-pdf/TimestampRequest.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[<15b24e6977023f8f031ff632a7b75f3a><15b24e6977023f8f031ff632a7b75f3a>] +[] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/models-pdf/TimestampResponse.pdf b/docs/models-pdf/TimestampResponse.pdf index 6c01b86..00aeccb 100644 --- a/docs/models-pdf/TimestampResponse.pdf +++ b/docs/models-pdf/TimestampResponse.pdf @@ -32,7 +32,7 @@ endobj endobj 6 0 obj << -/Author (\(anonymous\)) /CreationDate (D:20260909024418-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909024418-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (\(anonymous\)) /CreationDate (D:20260909025402-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260909025402-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False >> endobj @@ -62,7 +62,7 @@ xref trailer << /ID -[<6238e19d13481b62a4a5fa415f64c0ba><6238e19d13481b62a4a5fa415f64c0ba>] +[<14925b40184e6843f15b1b917d7c0c9c><14925b40184e6843f15b1b917d7c0c9c>] % ReportLab generated PDF document -- digest (opensource) /Info 6 0 R diff --git a/docs/modules/README.md b/docs/modules/README.md index 7c10b52..73f7506 100644 --- a/docs/modules/README.md +++ b/docs/modules/README.md @@ -43,7 +43,9 @@ One MD file per source module. - [verae-zapier-middleware/nats/connection](verae-zapier-middleware/nats/connection.md) - [verae-zapier-middleware/nats/publishers](verae-zapier-middleware/nats/publishers.md) - [verae-zapier-middleware/nats/subjects](verae-zapier-middleware/nats/subjects.md) +- [verae-zapier-middleware/nats/wait](verae-zapier-middleware/nats/wait.md) - [verae-zapier-middleware/routes/authRoutes](verae-zapier-middleware/routes/authRoutes.md) +- [verae-zapier-middleware/routes/hashRoutes](verae-zapier-middleware/routes/hashRoutes.md) - [verae-zapier-middleware/routes/index](verae-zapier-middleware/routes/index.md) - [verae-zapier-middleware/routes/statusRoutes](verae-zapier-middleware/routes/statusRoutes.md) - [verae-zapier-middleware/routes/tenantRoutes](verae-zapier-middleware/routes/tenantRoutes.md) @@ -66,6 +68,7 @@ One MD file per source module. - [verae-zapier-middleware/workers/webhookWorker](verae-zapier-middleware/workers/webhookWorker.md) - [verae-activate/authentication](verae-activate/authentication.md) - [verae-activate/creates/add_numbers](verae-activate/creates/add_numbers.md) +- [verae-activate/creates/echo](verae-activate/creates/echo.md) - [verae-activate/index](verae-activate/index.md) - [verae-zapier/authentication](verae-zapier/authentication.md) - [verae-zapier/creates/add_numbers](verae-zapier/creates/add_numbers.md) @@ -74,5 +77,6 @@ One MD file per source module. - [verae-zapier/creates/timestamp_and_wait](verae-zapier/creates/timestamp_and_wait.md) - [verae-zapier/creates/verify_timestamp](verae-zapier/creates/verify_timestamp.md) - [verae-zapier/index](verae-zapier/index.md) +- [verae-zapier/searches/hash_lookup](verae-zapier/searches/hash_lookup.md) - [verae-zapier/searches/job_status](verae-zapier/searches/job_status.md) - [verae-zapier/triggers/timestamp_completed](verae-zapier/triggers/timestamp_completed.md) diff --git a/docs/modules/verae-activate/creates/echo.md b/docs/modules/verae-activate/creates/echo.md new file mode 100644 index 0000000..7e39ba6 --- /dev/null +++ b/docs/modules/verae-activate/creates/echo.md @@ -0,0 +1,40 @@ +# `verae-activate/creates/echo` + +**Package:** `verae-activate` +**Source:** `packages/verae-activate/creates/echo.js` +**Lines:** 30 + +## What this module is + +Implementation module in `verae-activate`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`key`, `noun`, `display`, `label`, `description` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | +|------|------------|---------------------|---------|-----------------------------| +| `perform` | `_z, bundle` | — | `unknown` | see Call graph | + +## What it imports / requires + +_No imports detected._ + +## Call graph (identifiers invoked) + +`action`, `async` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/modules/verae-activate/index.md b/docs/modules/verae-activate/index.md index bb8809a..5303bf9 100644 --- a/docs/modules/verae-activate/index.md +++ b/docs/modules/verae-activate/index.md @@ -2,7 +2,7 @@ **Package:** `verae-activate` **Source:** `packages/verae-activate/index.js` -**Lines:** 21 +**Lines:** 23 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-activate`. The tables below are extracted from t ## Exports -`version`, `platformVersion`, `authentication`, `creates`, `addNumbers` +`version`, `platformVersion`, `authentication`, `creates`, `addNumbers`, `echo` ## Types / interfaces / classes @@ -24,6 +24,7 @@ _No top-level functions extracted._ - `./authentication` - `./creates/add_numbers` +- `./creates/echo` - `./package.json` - `zapier-platform-core` diff --git a/docs/modules/verae-zapier-middleware/clients/veraeClient.md b/docs/modules/verae-zapier-middleware/clients/veraeClient.md index ec0be38..00ec894 100644 --- a/docs/modules/verae-zapier-middleware/clients/veraeClient.md +++ b/docs/modules/verae-zapier-middleware/clients/veraeClient.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/clients/veraeClient.js` -**Lines:** 288 +**Lines:** 351 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract ## Exports -`veraeClient`, `clearMockJobs` +`sha256Hex`, `veraeClient`, `clearMockJobs` ## Types / interfaces / classes @@ -20,11 +20,13 @@ _None extracted._ | Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | |------|------------|---------------------|---------|-----------------------------| +| `sha256Hex` | `data` | data: `string` | `string` | see Call graph | | `delay` | `ms` | ms: `number` | `Promise` | see Call graph | | `mockLogin` | `{ username, password }` | ms: `number` | `Promise` | see Call graph | | `mockValidate` | `token` | — | `unknown` | see Call graph | -| `mockCreateTimestamp` | `{ data, hashAlg }` | — | `unknown` | see Call graph | +| `mockCreateTimestamp` | `{ data, hashAlg, sha256, publicMetadata, privateMetadata }` | — | `unknown` | see Call graph | | `mockGetStatus` | `jobId` | — | `unknown` | see Call graph | +| `mockLookupHash` | `sha256` | — | `unknown` | see Call graph | | `mockVerify` | `{ certificate }` | — | `unknown` | see Call graph | | `request` | `path, { method = 'GET', token, body } = {}` | path: `string` | `Promise` | see Call graph | | `clearMockJobs` | `(none)` | — | `void` | see Call graph | @@ -37,6 +39,7 @@ _None extracted._ | `login` | `credentials` | | `validate` | `token` | | `createTimestamp` | `token, body` | +| `lookupHash` | `token, sha256` | | `createBatchTimestamp` | `token, body` | | `getStatus` | `token, jobId` | | `getBatchStatus` | `token, body` | @@ -54,7 +57,7 @@ _None extracted._ ## Call graph (identifiers invoked) -`net`, `createDebugger`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `randomUUID`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateTimestamp`, `set`, `get`, `mockGetStatus`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createTimestamp`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear` +`net`, `createDebugger`, `sha256Hex`, `createHash`, `update`, `digest`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `randomUUID`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateTimestamp`, `toLowerCase`, `get`, `set`, `mockGetStatus`, `mockLookupHash`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createTimestamp`, `lookupHash`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/modules/verae-zapier-middleware/config.md b/docs/modules/verae-zapier-middleware/config.md index b9a679b..3484de9 100644 --- a/docs/modules/verae-zapier-middleware/config.md +++ b/docs/modules/verae-zapier-middleware/config.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/config.js` -**Lines:** 143 +**Lines:** 145 ## What this module is diff --git a/docs/modules/verae-zapier-middleware/nats/connection.md b/docs/modules/verae-zapier-middleware/nats/connection.md index de08f15..e934d63 100644 --- a/docs/modules/verae-zapier-middleware/nats/connection.md +++ b/docs/modules/verae-zapier-middleware/nats/connection.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/nats/connection.js` -**Lines:** 112 +**Lines:** 117 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract ## Exports -`connectNats`, `ensureStreams`, `closeNats`, `getJetStream`, `isNatsConnected` +`connectNats`, `getNatsConnection`, `ensureStreams`, `closeNats`, `getJetStream`, `isNatsConnected` ## Types / interfaces / classes @@ -21,6 +21,7 @@ _None extracted._ | Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | |------|------------|---------------------|---------|-----------------------------| | `connectNats` | `url = config.natsUrl` | url: `string` | `Promise<{ nc: import('nats').NatsConnection, js: import('nats').JetStreamClient, jsm: import('nats').JetStreamManager ` — >} | see Call graph | +| `getNatsConnection` | `(none)` | — | `import('nats').NatsConnection|null` | see Call graph | | `ensureStreams` | `manager` | manager: `import('nats').JetStreamManager` | `Promise` | see Call graph | | `closeNats` | `(none)` | — | `Promise` | see Call graph | | `getJetStream` | `(none)` | — | `import('nats').JetStreamClient|null` | see Call graph | @@ -34,7 +35,7 @@ _None extracted._ ## Call graph (identifiers invoked) -`createDebugger`, `import`, `connectNats`, `debug`, `disabled`, `info`, `connect`, `jetstream`, `jetstreamManager`, `ensureStreams`, `add`, `closeNats`, `drain`, `getJetStream`, `isNatsConnected`, `isClosed` +`createDebugger`, `import`, `connectNats`, `debug`, `disabled`, `info`, `connect`, `jetstream`, `jetstreamManager`, `getNatsConnection`, `ensureStreams`, `add`, `closeNats`, `drain`, `getJetStream`, `isNatsConnected`, `isClosed` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/modules/verae-zapier-middleware/nats/wait.md b/docs/modules/verae-zapier-middleware/nats/wait.md new file mode 100644 index 0000000..92fc8c3 --- /dev/null +++ b/docs/modules/verae-zapier-middleware/nats/wait.md @@ -0,0 +1,44 @@ +# `verae-zapier-middleware/nats/wait` + +**Package:** `verae-zapier-middleware` +**Source:** `packages/verae-zapier-middleware/src/nats/wait.js` +**Lines:** 60 + +## What this module is + +Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`waitForJobEvent` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | +|------|------------|---------------------|---------|-----------------------------| +| `waitForJobEvent` | `jobId, timeoutMs` | jobId: `string`, timeoutMs: `number` | `Promise` — event payload or null on timeout | see Call graph | +| `firstMatch` | `async (` | — | `unknown` | see Call graph | + +## What it imports / requires + +- `../debug/logger.js` +- `../debug/trace-context.js` +- `./subjects.js` +- `./connection.js` + +## Call graph (identifiers invoked) + +`createDebugger`, `waitForJobEvent`, `getNatsConnection`, `connectNats`, `subscribe`, `getTraceId`, `debug`, `setTimeout`, `resolve`, `async`, `parse`, `string`, `race`, `unsubscribe` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/modules/verae-zapier-middleware/routes/hashRoutes.md b/docs/modules/verae-zapier-middleware/routes/hashRoutes.md new file mode 100644 index 0000000..15d8f24 --- /dev/null +++ b/docs/modules/verae-zapier-middleware/routes/hashRoutes.md @@ -0,0 +1,46 @@ +# `verae-zapier-middleware/routes/hashRoutes` + +**Package:** `verae-zapier-middleware` +**Source:** `packages/verae-zapier-middleware/src/routes/hashRoutes.js` +**Lines:** 22 + +## What this module is + +Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`hashRoutes` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +_No top-level functions extracted._ + +## Methods (class / object) + +| Name | Parameters | +|------|------------| +| `asyncHandler` | `async (req, res` | + +## What it imports / requires + +- `express` +- `../errors.js` +- `../services/timestampService.js` + +## Call graph (identifiers invoked) + +`get`, `asyncHandler`, `async`, `test`, `lookupHash`, `json` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/modules/verae-zapier-middleware/routes/index.md b/docs/modules/verae-zapier-middleware/routes/index.md index beaf776..a01ba88 100644 --- a/docs/modules/verae-zapier-middleware/routes/index.md +++ b/docs/modules/verae-zapier-middleware/routes/index.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/routes/index.js` -**Lines:** 34 +**Lines:** 36 ## What this module is @@ -29,6 +29,7 @@ _No top-level functions extracted._ - `./statusRoutes.js` - `./webhookRoutes.js` - `./tenantRoutes.js` +- `./hashRoutes.js` - `../middleware/authenticate.js` - `../middleware/rateLimit.js` diff --git a/docs/modules/verae-zapier-middleware/routes/timestampRoutes.md b/docs/modules/verae-zapier-middleware/routes/timestampRoutes.md index d0270ee..9d7e6bd 100644 --- a/docs/modules/verae-zapier-middleware/routes/timestampRoutes.md +++ b/docs/modules/verae-zapier-middleware/routes/timestampRoutes.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/routes/timestampRoutes.js` -**Lines:** 50 +**Lines:** 62 ## What this module is diff --git a/docs/modules/verae-zapier-middleware/services/timestampService.md b/docs/modules/verae-zapier-middleware/services/timestampService.md index 5fa795b..d0a6552 100644 --- a/docs/modules/verae-zapier-middleware/services/timestampService.md +++ b/docs/modules/verae-zapier-middleware/services/timestampService.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/services/timestampService.js` -**Lines:** 123 +**Lines:** 160 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract ## Exports -`createTimestamp`, `createTimestampAndWait`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getJobVerification` +`createTimestamp`, `createTimestampAndWait`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getJobVerification`, `lookupHash` ## Types / interfaces / classes @@ -21,12 +21,13 @@ _None extracted._ | Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | |------|------------|---------------------|---------|-----------------------------| | `enqueueWatchForJob` | `ctx, jobId` | ctx: `object`, jobId: `string` | `unknown` | see Call graph | -| `createTimestamp` | `ctx, body` | ctx: `object` | `Promise<{ jobId: string ` — >} | see Call graph | +| `createTimestamp` | `ctx, body` | ctx: `object` | `Promise<{ jobId: string, sha256?: string, existing?: boolean ` — >} | see Call graph | | `createTimestampAndWait` | `ctx, body` | ctx: `object` | `Promise` — StatusResponse | see Call graph | | `createBatchTimestamp` | `ctx, body` | ctx: `object` | `unknown` | see Call graph | | `getJobStatus` | `ctx, jobId` | ctx: `object`, jobId: `string` | `unknown` | see Call graph | | `getBatchJobStatus` | `ctx, body` | ctx: `object` | `unknown` | see Call graph | | `getJobVerification` | `ctx, jobId` | ctx: `object`, jobId: `string` | `unknown` | see Call graph | +| `lookupHash` | `ctx, sha256` | ctx: `object`, sha256: `string` | `unknown` | see Call graph | ## Methods (class / object) @@ -40,12 +41,12 @@ _None extracted._ }` | | `checkEntitlement` | `ctx.tenantId, 'timestamp'` | | `recordUsage` | `ctx.tenantId, 'timestamp'` | -| `recordUsage` | `ctx.tenantId, 'status'` | | `checkEntitlement` | `ctx.tenantId, 'batch_timestamp', { amount: itemCount }` | | `recordUsage` | `ctx.tenantId, 'batch_timestamp', { amount: itemCount }` | | `recordUsage` | `ctx.tenantId, 'status'` | | `recordUsage` | `ctx.tenantId, 'status', { amount: body.jobIds?.length ?? 1 }` | | `recordUsage` | `ctx.tenantId, 'status'` | +| `recordUsage` | `ctx.tenantId, 'status'` | ## What it imports / requires @@ -58,7 +59,7 @@ _None extracted._ ## Call graph (identifiers invoked) -`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification` +`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJobEvent`, `getStatus`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification`, `lookupHash` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/modules/verae-zapier/index.md b/docs/modules/verae-zapier/index.md index 3c3851a..d0a5fc0 100644 --- a/docs/modules/verae-zapier/index.md +++ b/docs/modules/verae-zapier/index.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier` **Source:** `packages/verae-zapier/index.js` -**Lines:** 79 +**Lines:** 81 ## What this module is @@ -32,6 +32,7 @@ _None extracted._ - `./creates/batch_timestamp` - `./creates/add_numbers` - `./searches/job_status` +- `./searches/hash_lookup` - `./triggers/timestamp_completed` - `zapier-platform-core` - `./package.json` diff --git a/docs/modules/verae-zapier/searches/hash_lookup.md b/docs/modules/verae-zapier/searches/hash_lookup.md new file mode 100644 index 0000000..62a29ff --- /dev/null +++ b/docs/modules/verae-zapier/searches/hash_lookup.md @@ -0,0 +1,41 @@ +# `verae-zapier/searches/hash_lookup` + +**Package:** `verae-zapier` +**Source:** `packages/verae-zapier/searches/hash_lookup.js` +**Lines:** 35 + +## What this module is + +Implementation module in `verae-zapier`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`key`, `noun`, `display`, `label`, `description` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | +|------|------------|---------------------|---------|-----------------------------| +| `base` | `(none)` | — | `unknown` | see Call graph | +| `perform` | `z, bundle` | — | `unknown` | see Call graph | + +## What it imports / requires + +_No imports detected._ + +## Call graph (identifiers invoked) + +`async`, `request`, `base`, `encodeURIComponent`, `digest` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/modules/zappier/app.md b/docs/modules/zappier/app.md index b9a7be1..fe64428 100644 --- a/docs/modules/zappier/app.md +++ b/docs/modules/zappier/app.md @@ -2,7 +2,7 @@ **Package:** `zappier` **Source:** `packages/zappier/src/app.ts` -**Lines:** 203 +**Lines:** 237 ## What this module is @@ -47,7 +47,7 @@ Implementation module in `zappier`. The tables below are extracted from the sour ## Call graph (identifiers invoked) -`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `apiKeyAuth`, `middleware`, `get`, `meter`, `post`, `toUpperCase`, `isFinite`, `randomUUID`, `map`, `toISOString`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `find`, `applyMonthlyCredit` +`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `apiKeyAuth`, `middleware`, `get`, `meter`, `post`, `toUpperCase`, `toLowerCase`, `createHash`, `update`, `digest`, `randomUUID`, `toISOString`, `set`, `isFinite`, `map`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `find`, `applyMonthlyCredit` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/modules/zappier/pricing.md b/docs/modules/zappier/pricing.md index 56e397b..ed264d7 100644 --- a/docs/modules/zappier/pricing.md +++ b/docs/modules/zappier/pricing.md @@ -2,7 +2,7 @@ **Package:** `zappier` **Source:** `packages/zappier/src/pricing.ts` -**Lines:** 171 +**Lines:** 173 ## What this module is diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst index ad099ee..38d5777 100644 --- a/docs/sphinx/index.rst +++ b/docs/sphinx/index.rst @@ -1,21 +1,6 @@ Verae × Zapier module reference ================================= -.. toctree:: - :maxdepth: 1 - :caption: Models - - models/PriceRule - models/TierConfig - models/RateCard - models/Quote - models/CallUsage - models/Customer - models/AddNumbersResult - models/TimestampRequest - models/TimestampResponse - models/StatusResponse - .. toctree:: :maxdepth: 2 :caption: Modules @@ -61,7 +46,9 @@ Verae × Zapier module reference modules/verae-zapier-middleware/nats/connection modules/verae-zapier-middleware/nats/publishers modules/verae-zapier-middleware/nats/subjects + modules/verae-zapier-middleware/nats/wait modules/verae-zapier-middleware/routes/authRoutes + modules/verae-zapier-middleware/routes/hashRoutes modules/verae-zapier-middleware/routes/index modules/verae-zapier-middleware/routes/statusRoutes modules/verae-zapier-middleware/routes/tenantRoutes @@ -84,6 +71,7 @@ Verae × Zapier module reference modules/verae-zapier-middleware/workers/webhookWorker modules/verae-activate/authentication modules/verae-activate/creates/add_numbers + modules/verae-activate/creates/echo modules/verae-activate/index modules/verae-zapier/authentication modules/verae-zapier/creates/add_numbers @@ -92,5 +80,6 @@ Verae × Zapier module reference modules/verae-zapier/creates/timestamp_and_wait modules/verae-zapier/creates/verify_timestamp modules/verae-zapier/index + modules/verae-zapier/searches/hash_lookup modules/verae-zapier/searches/job_status modules/verae-zapier/triggers/timestamp_completed diff --git a/docs/sphinx/modules/verae-activate/creates/echo.md b/docs/sphinx/modules/verae-activate/creates/echo.md new file mode 100644 index 0000000..7e39ba6 --- /dev/null +++ b/docs/sphinx/modules/verae-activate/creates/echo.md @@ -0,0 +1,40 @@ +# `verae-activate/creates/echo` + +**Package:** `verae-activate` +**Source:** `packages/verae-activate/creates/echo.js` +**Lines:** 30 + +## What this module is + +Implementation module in `verae-activate`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`key`, `noun`, `display`, `label`, `description` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | +|------|------------|---------------------|---------|-----------------------------| +| `perform` | `_z, bundle` | — | `unknown` | see Call graph | + +## What it imports / requires + +_No imports detected._ + +## Call graph (identifiers invoked) + +`action`, `async` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/sphinx/modules/verae-activate/creates/echo.rst b/docs/sphinx/modules/verae-activate/creates/echo.rst new file mode 100644 index 0000000..472c65e --- /dev/null +++ b/docs/sphinx/modules/verae-activate/creates/echo.rst @@ -0,0 +1,7 @@ +verae-activate.creates.echo +=========================== + +Generated API sheet for ``verae-activate/creates/echo``. + +.. include:: echo.md + :literal: diff --git a/docs/sphinx/modules/verae-activate/index.md b/docs/sphinx/modules/verae-activate/index.md index bb8809a..5303bf9 100644 --- a/docs/sphinx/modules/verae-activate/index.md +++ b/docs/sphinx/modules/verae-activate/index.md @@ -2,7 +2,7 @@ **Package:** `verae-activate` **Source:** `packages/verae-activate/index.js` -**Lines:** 21 +**Lines:** 23 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-activate`. The tables below are extracted from t ## Exports -`version`, `platformVersion`, `authentication`, `creates`, `addNumbers` +`version`, `platformVersion`, `authentication`, `creates`, `addNumbers`, `echo` ## Types / interfaces / classes @@ -24,6 +24,7 @@ _No top-level functions extracted._ - `./authentication` - `./creates/add_numbers` +- `./creates/echo` - `./package.json` - `zapier-platform-core` diff --git a/docs/sphinx/modules/verae-zapier-middleware/clients/veraeClient.md b/docs/sphinx/modules/verae-zapier-middleware/clients/veraeClient.md index ec0be38..00ec894 100644 --- a/docs/sphinx/modules/verae-zapier-middleware/clients/veraeClient.md +++ b/docs/sphinx/modules/verae-zapier-middleware/clients/veraeClient.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/clients/veraeClient.js` -**Lines:** 288 +**Lines:** 351 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract ## Exports -`veraeClient`, `clearMockJobs` +`sha256Hex`, `veraeClient`, `clearMockJobs` ## Types / interfaces / classes @@ -20,11 +20,13 @@ _None extracted._ | Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | |------|------------|---------------------|---------|-----------------------------| +| `sha256Hex` | `data` | data: `string` | `string` | see Call graph | | `delay` | `ms` | ms: `number` | `Promise` | see Call graph | | `mockLogin` | `{ username, password }` | ms: `number` | `Promise` | see Call graph | | `mockValidate` | `token` | — | `unknown` | see Call graph | -| `mockCreateTimestamp` | `{ data, hashAlg }` | — | `unknown` | see Call graph | +| `mockCreateTimestamp` | `{ data, hashAlg, sha256, publicMetadata, privateMetadata }` | — | `unknown` | see Call graph | | `mockGetStatus` | `jobId` | — | `unknown` | see Call graph | +| `mockLookupHash` | `sha256` | — | `unknown` | see Call graph | | `mockVerify` | `{ certificate }` | — | `unknown` | see Call graph | | `request` | `path, { method = 'GET', token, body } = {}` | path: `string` | `Promise` | see Call graph | | `clearMockJobs` | `(none)` | — | `void` | see Call graph | @@ -37,6 +39,7 @@ _None extracted._ | `login` | `credentials` | | `validate` | `token` | | `createTimestamp` | `token, body` | +| `lookupHash` | `token, sha256` | | `createBatchTimestamp` | `token, body` | | `getStatus` | `token, jobId` | | `getBatchStatus` | `token, body` | @@ -54,7 +57,7 @@ _None extracted._ ## Call graph (identifiers invoked) -`net`, `createDebugger`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `randomUUID`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateTimestamp`, `set`, `get`, `mockGetStatus`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createTimestamp`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear` +`net`, `createDebugger`, `sha256Hex`, `createHash`, `update`, `digest`, `delay`, `setTimeout`, `mockLogin`, `now`, `toISOString`, `randomUUID`, `includes`, `mockValidate`, `startsWith`, `replace`, `mockCreateTimestamp`, `toLowerCase`, `get`, `set`, `mockGetStatus`, `mockLookupHash`, `mockVerify`, `request`, `debug`, `fetch`, `stringify`, `text`, `parse`, `error`, `client`, `login`, `validate`, `createTimestamp`, `lookupHash`, `createBatchTimestamp`, `push`, `getStatus`, `encodeURIComponent`, `getBatchStatus`, `verify`, `verifyBatch`, `getJobVerification`, `waitForJob`, `jobs`, `clearMockJobs`, `clear` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/sphinx/modules/verae-zapier-middleware/config.md b/docs/sphinx/modules/verae-zapier-middleware/config.md index b9a679b..3484de9 100644 --- a/docs/sphinx/modules/verae-zapier-middleware/config.md +++ b/docs/sphinx/modules/verae-zapier-middleware/config.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/config.js` -**Lines:** 143 +**Lines:** 145 ## What this module is diff --git a/docs/sphinx/modules/verae-zapier-middleware/nats/connection.md b/docs/sphinx/modules/verae-zapier-middleware/nats/connection.md index de08f15..e934d63 100644 --- a/docs/sphinx/modules/verae-zapier-middleware/nats/connection.md +++ b/docs/sphinx/modules/verae-zapier-middleware/nats/connection.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/nats/connection.js` -**Lines:** 112 +**Lines:** 117 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract ## Exports -`connectNats`, `ensureStreams`, `closeNats`, `getJetStream`, `isNatsConnected` +`connectNats`, `getNatsConnection`, `ensureStreams`, `closeNats`, `getJetStream`, `isNatsConnected` ## Types / interfaces / classes @@ -21,6 +21,7 @@ _None extracted._ | Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | |------|------------|---------------------|---------|-----------------------------| | `connectNats` | `url = config.natsUrl` | url: `string` | `Promise<{ nc: import('nats').NatsConnection, js: import('nats').JetStreamClient, jsm: import('nats').JetStreamManager ` — >} | see Call graph | +| `getNatsConnection` | `(none)` | — | `import('nats').NatsConnection|null` | see Call graph | | `ensureStreams` | `manager` | manager: `import('nats').JetStreamManager` | `Promise` | see Call graph | | `closeNats` | `(none)` | — | `Promise` | see Call graph | | `getJetStream` | `(none)` | — | `import('nats').JetStreamClient|null` | see Call graph | @@ -34,7 +35,7 @@ _None extracted._ ## Call graph (identifiers invoked) -`createDebugger`, `import`, `connectNats`, `debug`, `disabled`, `info`, `connect`, `jetstream`, `jetstreamManager`, `ensureStreams`, `add`, `closeNats`, `drain`, `getJetStream`, `isNatsConnected`, `isClosed` +`createDebugger`, `import`, `connectNats`, `debug`, `disabled`, `info`, `connect`, `jetstream`, `jetstreamManager`, `getNatsConnection`, `ensureStreams`, `add`, `closeNats`, `drain`, `getJetStream`, `isNatsConnected`, `isClosed` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/sphinx/modules/verae-zapier-middleware/nats/wait.md b/docs/sphinx/modules/verae-zapier-middleware/nats/wait.md new file mode 100644 index 0000000..92fc8c3 --- /dev/null +++ b/docs/sphinx/modules/verae-zapier-middleware/nats/wait.md @@ -0,0 +1,44 @@ +# `verae-zapier-middleware/nats/wait` + +**Package:** `verae-zapier-middleware` +**Source:** `packages/verae-zapier-middleware/src/nats/wait.js` +**Lines:** 60 + +## What this module is + +Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`waitForJobEvent` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | +|------|------------|---------------------|---------|-----------------------------| +| `waitForJobEvent` | `jobId, timeoutMs` | jobId: `string`, timeoutMs: `number` | `Promise` — event payload or null on timeout | see Call graph | +| `firstMatch` | `async (` | — | `unknown` | see Call graph | + +## What it imports / requires + +- `../debug/logger.js` +- `../debug/trace-context.js` +- `./subjects.js` +- `./connection.js` + +## Call graph (identifiers invoked) + +`createDebugger`, `waitForJobEvent`, `getNatsConnection`, `connectNats`, `subscribe`, `getTraceId`, `debug`, `setTimeout`, `resolve`, `async`, `parse`, `string`, `race`, `unsubscribe` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/sphinx/modules/verae-zapier-middleware/nats/wait.rst b/docs/sphinx/modules/verae-zapier-middleware/nats/wait.rst new file mode 100644 index 0000000..6a67058 --- /dev/null +++ b/docs/sphinx/modules/verae-zapier-middleware/nats/wait.rst @@ -0,0 +1,7 @@ +verae-zapier-middleware.nats.wait +================================= + +Generated API sheet for ``verae-zapier-middleware/nats/wait``. + +.. include:: wait.md + :literal: diff --git a/docs/sphinx/modules/verae-zapier-middleware/routes/hashRoutes.md b/docs/sphinx/modules/verae-zapier-middleware/routes/hashRoutes.md new file mode 100644 index 0000000..15d8f24 --- /dev/null +++ b/docs/sphinx/modules/verae-zapier-middleware/routes/hashRoutes.md @@ -0,0 +1,46 @@ +# `verae-zapier-middleware/routes/hashRoutes` + +**Package:** `verae-zapier-middleware` +**Source:** `packages/verae-zapier-middleware/src/routes/hashRoutes.js` +**Lines:** 22 + +## What this module is + +Implementation module in `verae-zapier-middleware`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`hashRoutes` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +_No top-level functions extracted._ + +## Methods (class / object) + +| Name | Parameters | +|------|------------| +| `asyncHandler` | `async (req, res` | + +## What it imports / requires + +- `express` +- `../errors.js` +- `../services/timestampService.js` + +## Call graph (identifiers invoked) + +`get`, `asyncHandler`, `async`, `test`, `lookupHash`, `json` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/sphinx/modules/verae-zapier-middleware/routes/hashRoutes.rst b/docs/sphinx/modules/verae-zapier-middleware/routes/hashRoutes.rst new file mode 100644 index 0000000..f613d99 --- /dev/null +++ b/docs/sphinx/modules/verae-zapier-middleware/routes/hashRoutes.rst @@ -0,0 +1,7 @@ +verae-zapier-middleware.routes.hashRoutes +========================================= + +Generated API sheet for ``verae-zapier-middleware/routes/hashRoutes``. + +.. include:: hashRoutes.md + :literal: diff --git a/docs/sphinx/modules/verae-zapier-middleware/routes/index.md b/docs/sphinx/modules/verae-zapier-middleware/routes/index.md index beaf776..a01ba88 100644 --- a/docs/sphinx/modules/verae-zapier-middleware/routes/index.md +++ b/docs/sphinx/modules/verae-zapier-middleware/routes/index.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/routes/index.js` -**Lines:** 34 +**Lines:** 36 ## What this module is @@ -29,6 +29,7 @@ _No top-level functions extracted._ - `./statusRoutes.js` - `./webhookRoutes.js` - `./tenantRoutes.js` +- `./hashRoutes.js` - `../middleware/authenticate.js` - `../middleware/rateLimit.js` diff --git a/docs/sphinx/modules/verae-zapier-middleware/routes/timestampRoutes.md b/docs/sphinx/modules/verae-zapier-middleware/routes/timestampRoutes.md index d0270ee..9d7e6bd 100644 --- a/docs/sphinx/modules/verae-zapier-middleware/routes/timestampRoutes.md +++ b/docs/sphinx/modules/verae-zapier-middleware/routes/timestampRoutes.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/routes/timestampRoutes.js` -**Lines:** 50 +**Lines:** 62 ## What this module is diff --git a/docs/sphinx/modules/verae-zapier-middleware/services/timestampService.md b/docs/sphinx/modules/verae-zapier-middleware/services/timestampService.md index 5fa795b..d0a6552 100644 --- a/docs/sphinx/modules/verae-zapier-middleware/services/timestampService.md +++ b/docs/sphinx/modules/verae-zapier-middleware/services/timestampService.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier-middleware` **Source:** `packages/verae-zapier-middleware/src/services/timestampService.js` -**Lines:** 123 +**Lines:** 160 ## What this module is @@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract ## Exports -`createTimestamp`, `createTimestampAndWait`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getJobVerification` +`createTimestamp`, `createTimestampAndWait`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getJobVerification`, `lookupHash` ## Types / interfaces / classes @@ -21,12 +21,13 @@ _None extracted._ | Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | |------|------------|---------------------|---------|-----------------------------| | `enqueueWatchForJob` | `ctx, jobId` | ctx: `object`, jobId: `string` | `unknown` | see Call graph | -| `createTimestamp` | `ctx, body` | ctx: `object` | `Promise<{ jobId: string ` — >} | see Call graph | +| `createTimestamp` | `ctx, body` | ctx: `object` | `Promise<{ jobId: string, sha256?: string, existing?: boolean ` — >} | see Call graph | | `createTimestampAndWait` | `ctx, body` | ctx: `object` | `Promise` — StatusResponse | see Call graph | | `createBatchTimestamp` | `ctx, body` | ctx: `object` | `unknown` | see Call graph | | `getJobStatus` | `ctx, jobId` | ctx: `object`, jobId: `string` | `unknown` | see Call graph | | `getBatchJobStatus` | `ctx, body` | ctx: `object` | `unknown` | see Call graph | | `getJobVerification` | `ctx, jobId` | ctx: `object`, jobId: `string` | `unknown` | see Call graph | +| `lookupHash` | `ctx, sha256` | ctx: `object`, sha256: `string` | `unknown` | see Call graph | ## Methods (class / object) @@ -40,12 +41,12 @@ _None extracted._ }` | | `checkEntitlement` | `ctx.tenantId, 'timestamp'` | | `recordUsage` | `ctx.tenantId, 'timestamp'` | -| `recordUsage` | `ctx.tenantId, 'status'` | | `checkEntitlement` | `ctx.tenantId, 'batch_timestamp', { amount: itemCount }` | | `recordUsage` | `ctx.tenantId, 'batch_timestamp', { amount: itemCount }` | | `recordUsage` | `ctx.tenantId, 'status'` | | `recordUsage` | `ctx.tenantId, 'status', { amount: body.jobIds?.length ?? 1 }` | | `recordUsage` | `ctx.tenantId, 'status'` | +| `recordUsage` | `ctx.tenantId, 'status'` | ## What it imports / requires @@ -58,7 +59,7 @@ _None extracted._ ## Call graph (identifiers invoked) -`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification` +`createDebugger`, `enqueueWatchForJob`, `getTraceId`, `import`, `enqueueWatch`, `debug`, `enqueueJob`, `createTimestamp`, `checkEntitlement`, `recordUsage`, `createTimestampAndWait`, `waitForJobEvent`, `getStatus`, `waitForJob`, `createBatchTimestamp`, `getJobStatus`, `getBatchJobStatus`, `getBatchStatus`, `getJobVerification`, `lookupHash` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/sphinx/modules/verae-zapier/index.md b/docs/sphinx/modules/verae-zapier/index.md index 3c3851a..d0a5fc0 100644 --- a/docs/sphinx/modules/verae-zapier/index.md +++ b/docs/sphinx/modules/verae-zapier/index.md @@ -2,7 +2,7 @@ **Package:** `verae-zapier` **Source:** `packages/verae-zapier/index.js` -**Lines:** 79 +**Lines:** 81 ## What this module is @@ -32,6 +32,7 @@ _None extracted._ - `./creates/batch_timestamp` - `./creates/add_numbers` - `./searches/job_status` +- `./searches/hash_lookup` - `./triggers/timestamp_completed` - `zapier-platform-core` - `./package.json` diff --git a/docs/sphinx/modules/verae-zapier/searches/hash_lookup.md b/docs/sphinx/modules/verae-zapier/searches/hash_lookup.md new file mode 100644 index 0000000..62a29ff --- /dev/null +++ b/docs/sphinx/modules/verae-zapier/searches/hash_lookup.md @@ -0,0 +1,41 @@ +# `verae-zapier/searches/hash_lookup` + +**Package:** `verae-zapier` +**Source:** `packages/verae-zapier/searches/hash_lookup.js` +**Lines:** 35 + +## What this module is + +Implementation module in `verae-zapier`. The tables below are extracted from the source (signatures + JSDoc). + +## Exports + +`key`, `noun`, `display`, `label`, `description` + +## Types / interfaces / classes + +_None extracted._ + +## Functions + +| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) | +|------|------------|---------------------|---------|-----------------------------| +| `base` | `(none)` | — | `unknown` | see Call graph | +| `perform` | `z, bundle` | — | `unknown` | see Call graph | + +## What it imports / requires + +_No imports detected._ + +## Call graph (identifiers invoked) + +`async`, `request`, `base`, `encodeURIComponent`, `digest` + +Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. + +## Return values (how to read this) + +- HTTP route handlers return Express `res.json(...)` bodies (see route docs). +- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches). +- Pricing functions return integer **cents** on `Quote.totalCents`. + diff --git a/docs/sphinx/modules/verae-zapier/searches/hash_lookup.rst b/docs/sphinx/modules/verae-zapier/searches/hash_lookup.rst new file mode 100644 index 0000000..0b31b5f --- /dev/null +++ b/docs/sphinx/modules/verae-zapier/searches/hash_lookup.rst @@ -0,0 +1,7 @@ +verae-zapier.searches.hash_lookup +================================= + +Generated API sheet for ``verae-zapier/searches/hash_lookup``. + +.. include:: hash_lookup.md + :literal: diff --git a/docs/sphinx/modules/zappier/app.md b/docs/sphinx/modules/zappier/app.md index b9a7be1..fe64428 100644 --- a/docs/sphinx/modules/zappier/app.md +++ b/docs/sphinx/modules/zappier/app.md @@ -2,7 +2,7 @@ **Package:** `zappier` **Source:** `packages/zappier/src/app.ts` -**Lines:** 203 +**Lines:** 237 ## What this module is @@ -47,7 +47,7 @@ Implementation module in `zappier`. The tables below are extracted from the sour ## Call graph (identifiers invoked) -`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `apiKeyAuth`, `middleware`, `get`, `meter`, `post`, `toUpperCase`, `isFinite`, `randomUUID`, `map`, `toISOString`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `find`, `applyMonthlyCredit` +`join`, `next`, `parse`, `status`, `json`, `buildApp`, `seeded`, `seedAdminUsersFromEnv`, `rateCard`, `getRateCard`, `tiers`, `getTiers`, `express`, `use`, `load`, `setup`, `adminLoginRouter`, `adminAuth`, `adminRouter`, `static`, `async`, `import`, `toDataURL`, `portalRouter`, `apiKeyAuth`, `middleware`, `get`, `meter`, `post`, `toUpperCase`, `toLowerCase`, `createHash`, `update`, `digest`, `randomUUID`, `toISOString`, `set`, `isFinite`, `map`, `unshift`, `filter`, `setUTCDate`, `setUTCHours`, `summaryFor`, `find`, `applyMonthlyCredit` Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types. diff --git a/docs/sphinx/modules/zappier/pricing.md b/docs/sphinx/modules/zappier/pricing.md index 56e397b..ed264d7 100644 --- a/docs/sphinx/modules/zappier/pricing.md +++ b/docs/sphinx/modules/zappier/pricing.md @@ -2,7 +2,7 @@ **Package:** `zappier` **Source:** `packages/zappier/src/pricing.ts` -**Lines:** 171 +**Lines:** 173 ## What this module is diff --git a/harness/scripts/smoke.sh b/harness/scripts/smoke.sh index c1f4ef3..649ae43 100755 --- a/harness/scripts/smoke.sh +++ b/harness/scripts/smoke.sh @@ -1,10 +1,49 @@ #!/usr/bin/env bash -# Disconnected smoke (expand in PR 3 / PR 4). +# GATE 12 — disconnected E2E: middleware health, signup, timestamp, wait (NATS off). set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -echo "workspace: $ROOT" -curl -fsS "http://127.0.0.1:3100/health" || { - echo "middleware /health not up — start packages/verae-zapier-middleware with MOCK_VERAE=true" - exit 1 +export MOCK_VERAE=true +export NATS_ENABLED=false +export PORT="${PORT:-3100}" +export TOKEN_SECRET=dev-secret +cd "$ROOT/packages/verae-zapier-middleware" + +started=0 +if ! curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then + node src/index.js >/tmp/verae-mw-smoke.log 2>&1 & + started=$! + for i in $(seq 1 40); do + if curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then + break + fi + sleep 0.15 + done +fi + +cleanup() { + if [[ "$started" != "0" ]]; then + kill "$started" 2>/dev/null || true + fi } -echo "middleware health ok" +trap cleanup EXIT + +curl -fsS "http://127.0.0.1:${PORT}/health" | grep -q ok +echo "health ok" + +signup=$(curl -fsS -X POST "http://127.0.0.1:${PORT}/zapier/v1/signup" \ + -H 'content-type: application/json' \ + -d '{"email":"smoke@example.com","name":"smoke","veraeUsername":"smokeuser","veraePassword":"smokepass"}') +key=$(node -e "const j=JSON.parse(process.argv[1]); process.stdout.write(j.apiKey||'')" "$signup") +if [[ -z "$key" ]]; then + echo "signup did not return apiKey: $signup" >&2 + exit 1 +fi +echo "signup ok" + +wait=$(curl -fsS -X POST "http://127.0.0.1:${PORT}/zapier/v1/timestamp/wait" \ + -H "authorization: Bearer $key" \ + -H 'content-type: application/json' \ + -d '{"data":"smoke-payload"}') +echo "$wait" | grep -q completed +echo "timestamp wait ok" +echo "GATE 12 PASSED" diff --git a/package.json b/package.json index d0e3ab4..b9c391c 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ "gate:6": "npm --prefix packages/verae-zapier-middleware run test:gate6", "gate:7": "npm --prefix packages/verae-zapier-middleware run test:gate7", "gate:8": "npm --prefix packages/verae-zapier-middleware run test:gate8", - "gate:9": "node scripts/gate-not-ready.mjs 9", + "gate:9": "npm --prefix packages/verae-zapier-middleware run test:gate9", "gate:10": "npm --prefix packages/verae-zapier-middleware run test:gate10", "gate:11": "node --test packages/verae-zapier/test/app.test.js", - "gate:12": "node scripts/gate-not-ready.mjs 12", + "gate:12": "bash harness/scripts/smoke.sh", "gate:13": "node scripts/gate-not-ready.mjs 13", "gate:all": "node scripts/gate-all.mjs", "test:middleware": "npm --prefix packages/verae-zapier-middleware test", diff --git a/packages/verae-activate/creates/echo.js b/packages/verae-activate/creates/echo.js new file mode 100644 index 0000000..0ad7175 --- /dev/null +++ b/packages/verae-activate/creates/echo.js @@ -0,0 +1,29 @@ +/** + * Echo — returns the input string. Second activate-now action (no backend). + * @module creates/echo + */ + +const perform = async (_z, bundle) => { + const text = String(bundle.inputData.text ?? ''); + return { text, length: text.length, mode: 'local' }; +}; + +module.exports = { + key: 'echo', + noun: 'Echo', + display: { + label: 'Echo Text', + description: 'Returns the text you send. No API required.', + }, + operation: { + cleanInputData: false, + inputFields: [{ key: 'text', label: 'Text', type: 'string', required: true }], + perform, + sample: { text: 'hello', length: 5, mode: 'local' }, + outputFields: [ + { key: 'text', type: 'string' }, + { key: 'length', type: 'integer' }, + { key: 'mode', type: 'string' }, + ], + }, +}; diff --git a/packages/verae-activate/index.js b/packages/verae-activate/index.js index 934a339..97c8c57 100644 --- a/packages/verae-activate/index.js +++ b/packages/verae-activate/index.js @@ -9,6 +9,7 @@ const authentication = require('./authentication'); const addNumbers = require('./creates/add_numbers'); +const echo = require('./creates/echo'); module.exports = { version: require('./package.json').version, @@ -16,5 +17,6 @@ module.exports = { authentication, creates: { [addNumbers.key]: addNumbers, + [echo.key]: echo, }, }; diff --git a/packages/verae-activate/test/add_numbers.test.js b/packages/verae-activate/test/add_numbers.test.js index a7b4a42..56d22f1 100644 --- a/packages/verae-activate/test/add_numbers.test.js +++ b/packages/verae-activate/test/add_numbers.test.js @@ -13,6 +13,7 @@ describe('verae-activate app', () => { it('exports Add Numbers create and custom auth', () => { assert.equal(authentication.type, 'custom'); assert.ok(App.creates.add_numbers); + assert.ok(App.creates.echo); assert.equal(addNumbers.key, 'add_numbers'); assert.ok(addNumbers.operation.inputFields.some((f) => f.key === 'number1')); assert.ok(addNumbers.operation.inputFields.some((f) => f.key === 'number2')); @@ -63,6 +64,13 @@ describe('verae-activate app', () => { assert.equal(result.sum, 5); }); + it('echo returns text length', async () => { + const echo = require('../creates/echo'); + const out = await echo.operation.perform({}, { inputData: { text: 'ab' }, authData: {} }); + assert.equal(out.length, 2); + assert.equal(out.mode, 'local'); + }); + it('auth test is local when fields empty', async () => { const out = await authentication.test({ request: async () => ({ data: {} }) }, { authData: {} }); assert.equal(out.ok, true); diff --git a/packages/verae-zapier-middleware/.env.example b/packages/verae-zapier-middleware/.env.example index 16eb359..b7dc81e 100644 --- a/packages/verae-zapier-middleware/.env.example +++ b/packages/verae-zapier-middleware/.env.example @@ -7,8 +7,13 @@ VERAE_API_BASE_URL=https://api.veraetime.net MOCK_VERAE=false # NATS (Phase 7+) +# NS1 JetStream is 127.0.0.1:4222 on 70.88.205.138 (not public). +# Tunnel: ssh -fN -L 14222:127.0.0.1:4222 marchon@70.88.205.138 +# then NATS_URL=nats://127.0.0.1:14222 NATS_ENABLED=false NATS_URL=nats://127.0.0.1:4222 +WAIT_TIMEOUT_MS=25000 +MOCK_JOB_COMPLETE_MS=150 # Security TOKEN_SECRET=change-me-in-production diff --git a/packages/verae-zapier-middleware/package.json b/packages/verae-zapier-middleware/package.json index 7b31a6e..5f89495 100644 --- a/packages/verae-zapier-middleware/package.json +++ b/packages/verae-zapier-middleware/package.json @@ -17,7 +17,9 @@ "test:gate6": "MOCK_VERAE=true NATS_ENABLED=false JOB_POLL_INTERVAL_MS=20 JOB_POLL_MAX_ATTEMPTS=50 node --test test/integration/http-api.test.js", "test:gate7": "MOCK_VERAE=true NATS_ENABLED=true NATS_FORCE_CONNECT=1 NATS_URL=nats://127.0.0.1:4222 node --test test/integration/nats.test.js", "test:gate8": "MOCK_VERAE=true NATS_ENABLED=true NATS_FORCE_CONNECT=1 NATS_URL=nats://127.0.0.1:4222 JOB_POLL_INTERVAL_MS=50 node --test test/integration/nats-workers.test.js", - "test:gate10": "MOCK_VERAE=true NATS_ENABLED=false node --test test/integration/tenants.test.js" + "test:gate9": "MOCK_VERAE=true NATS_ENABLED=true NATS_FORCE_CONNECT=1 NATS_URL=nats://127.0.0.1:4222 JOB_POLL_INTERVAL_MS=40 WAIT_TIMEOUT_MS=8000 node --test test/integration/wait-nats.test.js", + "test:gate10": "MOCK_VERAE=true NATS_ENABLED=false node --test test/integration/tenants.test.js", + "test:hash": "MOCK_VERAE=true NATS_ENABLED=false node --test test/integration/hash.test.js" }, "engines": { "node": ">=22.0.0" diff --git a/packages/verae-zapier-middleware/src/clients/veraeClient.js b/packages/verae-zapier-middleware/src/clients/veraeClient.js index d0c7f4d..0869d4d 100644 --- a/packages/verae-zapier-middleware/src/clients/veraeClient.js +++ b/packages/verae-zapier-middleware/src/clients/veraeClient.js @@ -3,7 +3,7 @@ * @module clients/veraeClient */ -import { randomUUID } from 'node:crypto'; +import { createHash, randomUUID } from 'node:crypto'; import { config } from '../config.js'; import { AppError } from '../errors.js'; import { createDebugger } from '../debug/logger.js'; @@ -11,6 +11,16 @@ import { createDebugger } from '../debug/logger.js'; const log = createDebugger('http'); const mockJobs = new Map(); +/** @type {Map} */ +const mockHashes = new Map(); + +/** + * @param {string} data + * @returns {string} + */ +export function sha256Hex(data) { + return createHash('sha256').update(String(data), 'utf8').digest('hex'); +} /** * @param {number} ms @@ -50,19 +60,36 @@ async function mockValidate(token) { }; } -async function mockCreateTimestamp({ data, hashAlg }) { - if (!data) { +async function mockCreateTimestamp({ data, hashAlg, sha256, publicMetadata, privateMetadata }) { + if (!data && !sha256) { throw new AppError('Invalid input data', { status: 400, code: 'VALIDATION_ERROR' }); } + const key = (sha256 || sha256Hex(data)).toLowerCase(); + const existing = mockHashes.get(key); + if (existing) { + const job = mockJobs.get(existing.jobId); + return { + jobId: existing.jobId, + sha256: key, + existing: true, + timestamp: job?.completedAt ?? existing.timestamp, + }; + } + const jobId = randomUUID(); mockJobs.set(jobId, { id: jobId, status: 'pending', createdAt: Date.now(), - data, + data: data ?? key, hashAlg: hashAlg ?? 'SHA256', + sha256: key, + publicMetadata: publicMetadata ?? {}, + privateMetadata: privateMetadata ?? {}, }); + mockHashes.set(key, { jobId, sha256: key, publicMetadata, privateMetadata }); + const delayMs = config.mockJobCompleteMs ?? 150; setTimeout(() => { const job = mockJobs.get(jobId); if (!job) return; @@ -73,10 +100,14 @@ async function mockCreateTimestamp({ data, hashAlg }) { blockIndex: 42, timestamp: job.completedAt, certificate: job.result, + sha256: key, + publicMetadata: job.publicMetadata, }; - }, 150); + const rec = mockHashes.get(key); + if (rec) rec.timestamp = job.completedAt; + }, delayMs); - return { jobId }; + return { jobId, sha256: key, existing: false }; } async function mockGetStatus(jobId) { @@ -91,6 +122,24 @@ async function mockGetStatus(jobId) { completedAt: job.completedAt, metadata: job.metadata, error: job.error, + sha256: job.sha256, + }; +} + +async function mockLookupHash(sha256) { + const key = String(sha256 || '').toLowerCase(); + const rec = mockHashes.get(key); + if (!rec) { + throw new AppError('Hash not found', { status: 404, code: 'NOT_FOUND' }); + } + const job = mockJobs.get(rec.jobId); + return { + sha256: key, + exists: true, + jobId: rec.jobId, + timestamp: rec.timestamp ?? job?.completedAt, + status: job?.status, + publicMetadata: job?.publicMetadata ?? rec.publicMetadata ?? {}, }; } @@ -184,7 +233,20 @@ export const veraeClient = { */ async createTimestamp(token, body) { if (config.mockVerae) return mockCreateTimestamp(body); - return request('/api/timestamp', { method: 'POST', token, body }); + const liveBody = { data: body.data, hashAlg: body.hashAlg }; + return request('/api/timestamp', { method: 'POST', token, body: liveBody }); + }, + + /** + * @param {string} token + * @param {string} sha256 + */ + async lookupHash(token, sha256) { + if (config.mockVerae) return mockLookupHash(sha256); + throw new AppError('Hash lookup is not on the live Verae OpenAPI', { + status: 501, + code: 'NOT_IMPLEMENTED', + }); }, /** @@ -284,4 +346,5 @@ export const veraeClient = { */ export function clearMockJobs() { mockJobs.clear(); + mockHashes.clear(); } diff --git a/packages/verae-zapier-middleware/src/config.js b/packages/verae-zapier-middleware/src/config.js index c06935c..c52e160 100644 --- a/packages/verae-zapier-middleware/src/config.js +++ b/packages/verae-zapier-middleware/src/config.js @@ -87,6 +87,8 @@ export const config = { tokenSecret: process.env.TOKEN_SECRET ?? 'dev-secret-change-me', jobPollIntervalMs: int(process.env.JOB_POLL_INTERVAL_MS, 2000), jobPollMaxAttempts: int(process.env.JOB_POLL_MAX_ATTEMPTS, 60), + waitTimeoutMs: int(process.env.WAIT_TIMEOUT_MS, 25000), + mockJobCompleteMs: int(process.env.MOCK_JOB_COMPLETE_MS, 150), storePath: resolve(rootDir, process.env.STORE_PATH ?? './data/store.json'), upgradeUrl: process.env.UPGRADE_URL ?? 'https://veraetime.net/billing', adminSecret: process.env.ADMIN_SECRET ?? 'change-me-admin', diff --git a/packages/verae-zapier-middleware/src/nats/connection.js b/packages/verae-zapier-middleware/src/nats/connection.js index b79c46c..5630b38 100644 --- a/packages/verae-zapier-middleware/src/nats/connection.js +++ b/packages/verae-zapier-middleware/src/nats/connection.js @@ -43,6 +43,11 @@ export async function connectNats(url = config.natsUrl) { return { nc, js, jsm }; } +/** @returns {import('nats').NatsConnection|null} */ +export function getNatsConnection() { + return nc; +} + /** * Idempotently create JetStream streams required by this middleware. * diff --git a/packages/verae-zapier-middleware/src/nats/wait.js b/packages/verae-zapier-middleware/src/nats/wait.js new file mode 100644 index 0000000..4dd9e6c --- /dev/null +++ b/packages/verae-zapier-middleware/src/nats/wait.js @@ -0,0 +1,59 @@ +/** + * Wait for a terminal job event on NATS JOBS_EVENTS. + * @module nats/wait + */ + +import { createDebugger } from '../debug/logger.js'; +import { getTraceId } from '../debug/trace-context.js'; +import { SUBJECTS } from './subjects.js'; +import { connectNats, getNatsConnection } from './connection.js'; + +const log = createDebugger('jobs'); + +/** + * @param {string} jobId + * @param {number} timeoutMs + * @returns {Promise} event payload or null on timeout + */ +export async function waitForJobEvent(jobId, timeoutMs) { + const handles = getNatsConnection() ? { nc: getNatsConnection() } : await connectNats(); + const nc = handles.nc; + const sub = nc.subscribe(SUBJECTS.JOBS_EVENTS); + const traceId = getTraceId(); + log.debug('waitForJobEvent subscribe', { jobId, timeoutMs, traceId }); + + const timeout = new Promise((resolve) => { + setTimeout(() => resolve(null), timeoutMs); + }); + + const firstMatch = (async () => { + for await (const msg of sub) { + let data; + try { + data = JSON.parse(typeof msg.string === 'function' ? msg.string() : msg.data); + } catch { + continue; + } + if (data.jobId !== jobId) continue; + const event = data.event; + if ( + event === 'timestamp.completed' || + event === 'timestamp.failed' || + event === 'timestamp.timeout' + ) { + return data; + } + } + return null; + })(); + + try { + return await Promise.race([firstMatch, timeout]); + } finally { + try { + sub.unsubscribe(); + } catch { + /* ignore */ + } + } +} diff --git a/packages/verae-zapier-middleware/src/routes/hashRoutes.js b/packages/verae-zapier-middleware/src/routes/hashRoutes.js new file mode 100644 index 0000000..a6f8d8a --- /dev/null +++ b/packages/verae-zapier-middleware/src/routes/hashRoutes.js @@ -0,0 +1,21 @@ +/** + * @module routes/hashRoutes + */ + +import { Router } from 'express'; +import { asyncHandler, AppError } from '../errors.js'; +import { lookupHash } from '../services/timestampService.js'; + +export const hashRoutes = Router(); + +hashRoutes.get( + '/:sha256', + asyncHandler(async (req, res) => { + const { sha256 } = req.params; + if (!sha256 || !/^[a-fA-F0-9]{64}$/.test(sha256)) { + throw new AppError('sha256 must be 64 hex chars', { status: 400, code: 'VALIDATION_ERROR' }); + } + const result = await lookupHash(req.auth, sha256); + res.json(result); + }), +); diff --git a/packages/verae-zapier-middleware/src/routes/index.js b/packages/verae-zapier-middleware/src/routes/index.js index 75b64a1..0b38cc3 100644 --- a/packages/verae-zapier-middleware/src/routes/index.js +++ b/packages/verae-zapier-middleware/src/routes/index.js @@ -10,6 +10,7 @@ import { verifyRoutes } from './verifyRoutes.js'; import { statusRoutes } from './statusRoutes.js'; import { webhookRoutes } from './webhookRoutes.js'; import { publicTenantRoutes, adminTenantRoutes } from './tenantRoutes.js'; +import { hashRoutes } from './hashRoutes.js'; import { authenticate } from '../middleware/authenticate.js'; import { rateLimit } from '../middleware/rateLimit.js'; @@ -29,5 +30,6 @@ protectedRoutes.use('/timestamp', timestampRoutes); protectedRoutes.use('/verify', verifyRoutes); protectedRoutes.use('/status', statusRoutes); protectedRoutes.use('/webhooks', webhookRoutes); +protectedRoutes.use('/hashes', hashRoutes); apiRoutes.use('/v1', protectedRoutes); diff --git a/packages/verae-zapier-middleware/src/routes/timestampRoutes.js b/packages/verae-zapier-middleware/src/routes/timestampRoutes.js index 6b09077..ca4dc87 100644 --- a/packages/verae-zapier-middleware/src/routes/timestampRoutes.js +++ b/packages/verae-zapier-middleware/src/routes/timestampRoutes.js @@ -15,11 +15,17 @@ export const timestampRoutes = Router(); timestampRoutes.post( '/', asyncHandler(async (req, res) => { - const { data, hashAlg } = req.body ?? {}; - if (!data) { - throw new AppError('data is required', { status: 400, code: 'VALIDATION_ERROR' }); + const { data, hashAlg, sha256, publicMetadata, privateMetadata } = req.body ?? {}; + if (!data && !sha256) { + throw new AppError('data or sha256 is required', { status: 400, code: 'VALIDATION_ERROR' }); } - const result = await createTimestamp(req.auth, { data, hashAlg }); + const result = await createTimestamp(req.auth, { + data, + hashAlg, + sha256, + publicMetadata, + privateMetadata, + }); res.status(202).json(result); }), ); @@ -27,11 +33,17 @@ timestampRoutes.post( timestampRoutes.post( '/wait', asyncHandler(async (req, res) => { - const { data, hashAlg } = req.body ?? {}; - if (!data) { - throw new AppError('data is required', { status: 400, code: 'VALIDATION_ERROR' }); + const { data, hashAlg, sha256, publicMetadata, privateMetadata } = req.body ?? {}; + if (!data && !sha256) { + throw new AppError('data or sha256 is required', { status: 400, code: 'VALIDATION_ERROR' }); } - const result = await createTimestampAndWait(req.auth, { data, hashAlg }); + const result = await createTimestampAndWait(req.auth, { + data, + hashAlg, + sha256, + publicMetadata, + privateMetadata, + }); res.json(result); }), ); diff --git a/packages/verae-zapier-middleware/src/services/timestampService.js b/packages/verae-zapier-middleware/src/services/timestampService.js index abdfc75..a3b69c6 100644 --- a/packages/verae-zapier-middleware/src/services/timestampService.js +++ b/packages/verae-zapier-middleware/src/services/timestampService.js @@ -45,8 +45,8 @@ async function enqueueWatchForJob(ctx, jobId) { /** * @param {object} ctx - Auth context with tenantId, veraeToken - * @param {{ data: string, hashAlg?: string }} body - * @returns {Promise<{ jobId: string }>} + * @param {{ data?: string, hashAlg?: string, sha256?: string, publicMetadata?: object, privateMetadata?: object }} body + * @returns {Promise<{ jobId: string, sha256?: string, existing?: boolean }>} */ export async function createTimestamp(ctx, body) { checkEntitlement(ctx.tenantId, 'timestamp'); @@ -66,12 +66,39 @@ export async function createTimestamp(ctx, body) { */ export async function createTimestampAndWait(ctx, body) { const created = await createTimestamp(ctx, body); - const status = await veraeClient.waitForJob(ctx.veraeToken, created.jobId, { - maxAttempts: config.jobPollMaxAttempts, - intervalMs: config.jobPollIntervalMs, - }); - recordUsage(ctx.tenantId, 'status'); - return status; + + if (config.natsEnabled) { + const { waitForJobEvent } = await import('../nats/wait.js'); + const event = await waitForJobEvent(created.jobId, config.waitTimeoutMs); + if (event?.status) { + recordUsage(ctx.tenantId, 'status'); + log.debug('wait via NATS event', { + jobId: created.jobId, + event: event.event, + traceId: event.traceId, + }); + return event.status; + } + const status = await veraeClient.getStatus(ctx.veraeToken, created.jobId); + recordUsage(ctx.tenantId, 'status'); + if (status.status === 'pending') { + return { id: created.jobId, jobId: created.jobId, status: 'pending' }; + } + return status; + } + + try { + const status = await veraeClient.waitForJob(ctx.veraeToken, created.jobId, { + maxAttempts: config.jobPollMaxAttempts, + intervalMs: config.jobPollIntervalMs, + }); + recordUsage(ctx.tenantId, 'status'); + return status; + } catch { + const status = await veraeClient.getStatus(ctx.veraeToken, created.jobId); + recordUsage(ctx.tenantId, 'status'); + return { id: created.jobId, jobId: created.jobId, status: status.status ?? 'pending' }; + } } /** @@ -120,3 +147,13 @@ export async function getJobVerification(ctx, jobId) { recordUsage(ctx.tenantId, 'status'); return status; } + +/** + * @param {object} ctx + * @param {string} sha256 + */ +export async function lookupHash(ctx, sha256) { + const result = await veraeClient.lookupHash(ctx.veraeToken, sha256); + recordUsage(ctx.tenantId, 'status'); + return result; +} diff --git a/packages/verae-zapier-middleware/test/integration/hash.test.js b/packages/verae-zapier-middleware/test/integration/hash.test.js new file mode 100644 index 0000000..4b2dfed --- /dev/null +++ b/packages/verae-zapier-middleware/test/integration/hash.test.js @@ -0,0 +1,97 @@ +/** + * Hash idempotent register + lookup (mock Verae) + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import http from 'node:http'; +import { createApp } from '../../src/app.js'; +import { useTempStore, seedProTenant } from '../helpers.js'; +import { clearMockJobs, sha256Hex } from '../../src/clients/veraeClient.js'; +import { config } from '../../src/config.js'; + +describe('hash register + lookup', () => { + let ctx; + /** @type {import('http').Server} */ + let server; + let port; + let apiKey; + + before(async () => { + config.mockVerae = true; + config.natsEnabled = false; + clearMockJobs(); + ctx = useTempStore(); + const seeded = seedProTenant(); + apiKey = seeded.apiKey; + const app = createApp(); + await new Promise((resolve) => { + server = app.listen(0, '127.0.0.1', () => { + port = server.address().port; + resolve(); + }); + }); + }); + + after(async () => { + await new Promise((r) => server.close(r)); + ctx.cleanup(); + }); + + it('registers sha256, second create returns original jobId', async () => { + const data = 'hello-hash'; + const sha = sha256Hex(data); + const r1 = await json(port, 'POST', '/zapier/v1/timestamp', apiKey, { data }); + assert.equal(r1.status, 202); + const r2 = await json(port, 'POST', '/zapier/v1/timestamp', apiKey, { sha256: sha }); + assert.equal(r2.status, 202); + assert.equal(r2.body.jobId, r1.body.jobId); + assert.equal(r2.body.existing, true); + + const look = await json(port, 'GET', `/zapier/v1/hashes/${sha}`, apiKey); + assert.equal(look.status, 200); + assert.equal(look.body.exists, true); + assert.equal(look.body.jobId, r1.body.jobId); + }); + + it('lookup miss is 404', async () => { + const miss = 'a'.repeat(64); + const look = await json(port, 'GET', `/zapier/v1/hashes/${miss}`, apiKey); + assert.equal(look.status, 404); + }); +}); + +function json(port, method, path, apiKey, body) { + return new Promise((resolve, reject) => { + const req = http.request( + { + hostname: '127.0.0.1', + port, + path, + method, + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + }, + }, + (res) => { + let data = ''; + res.on('data', (c) => { + data += c; + }); + res.on('end', () => { + let parsed = {}; + try { + parsed = data ? JSON.parse(data) : {}; + } catch { + parsed = { raw: data }; + } + resolve({ status: res.statusCode, body: parsed }); + }); + }, + ); + req.on('error', reject); + if (body) req.write(JSON.stringify(body)); + req.end(); + }); +} diff --git a/packages/verae-zapier-middleware/test/integration/wait-nats.test.js b/packages/verae-zapier-middleware/test/integration/wait-nats.test.js new file mode 100644 index 0000000..eb48462 --- /dev/null +++ b/packages/verae-zapier-middleware/test/integration/wait-nats.test.js @@ -0,0 +1,67 @@ +/** + * GATE 9 — /timestamp/wait via NATS events (timeout returns pending + jobId) + */ + +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { config } from '../../src/config.js'; +import { useTempStore, seedProTenant } from '../helpers.js'; +import { connectNats, ensureStreams, closeNats } from '../../src/nats/connection.js'; +import { startJobPollerWorker, stopJobPollerWorker } from '../../src/workers/jobPollerWorker.js'; +import { createTimestampAndWait } from '../../src/services/timestampService.js'; +import { resolveAuthContext } from '../../src/services/authService.js'; +import { clearMockJobs } from '../../src/clients/veraeClient.js'; + +describe('GATE 9 wait via NATS', () => { + /** @type {ReturnType} */ + let ctx; + let authCtx; + + before(async () => { + assert.equal(config.mockVerae, true); + config.natsEnabled = true; + process.env.NATS_FORCE_CONNECT = '1'; + config.natsUrl = process.env.NATS_URL || 'nats://127.0.0.1:4222'; + config.jobPollIntervalMs = 40; + config.jobPollMaxAttempts = 40; + config.waitTimeoutMs = 8000; + config.mockJobCompleteMs = 80; + + clearMockJobs(); + ctx = useTempStore(); + const seeded = seedProTenant(); + authCtx = await resolveAuthContext(seeded.apiKey); + + await connectNats(config.natsUrl); + await ensureStreams(); + await startJobPollerWorker(); + }); + + after(async () => { + await stopJobPollerWorker(); + await closeNats(); + ctx.cleanup(); + process.env.NATS_FORCE_CONNECT = ''; + }); + + it('wait returns completed when worker finishes before timeout', async () => { + const status = await createTimestampAndWait(authCtx, { data: 'wait-fast' }); + assert.equal(status.status, 'completed'); + assert.ok(status.id || status.jobId); + }); + + it('wait returns pending + jobId on timeout', async () => { + const prevWait = config.waitTimeoutMs; + const prevDelay = config.mockJobCompleteMs; + config.waitTimeoutMs = 60; + config.mockJobCompleteMs = 30_000; + try { + const status = await createTimestampAndWait(authCtx, { data: 'wait-slow' }); + assert.equal(status.status, 'pending'); + assert.ok(status.jobId || status.id); + } finally { + config.waitTimeoutMs = prevWait; + config.mockJobCompleteMs = prevDelay; + } + }); +}); diff --git a/packages/verae-zapier-middleware/test/unit/config.test.js b/packages/verae-zapier-middleware/test/unit/config.test.js index e979063..fed57f9 100644 --- a/packages/verae-zapier-middleware/test/unit/config.test.js +++ b/packages/verae-zapier-middleware/test/unit/config.test.js @@ -18,6 +18,7 @@ describe('config', () => { 'tokenSecret', 'jobPollIntervalMs', 'jobPollMaxAttempts', + 'waitTimeoutMs', 'storePath', ]) { assert.notEqual(config[key], undefined, `missing config.${key}`); diff --git a/packages/verae-zapier/index.js b/packages/verae-zapier/index.js index 3034d49..c73a3f6 100644 --- a/packages/verae-zapier/index.js +++ b/packages/verae-zapier/index.js @@ -10,6 +10,7 @@ const verifyTimestamp = require('./creates/verify_timestamp'); const batchTimestamp = require('./creates/batch_timestamp'); const addNumbers = require('./creates/add_numbers'); const jobStatus = require('./searches/job_status'); +const hashLookup = require('./searches/hash_lookup'); const timestampCompleted = require('./triggers/timestamp_completed'); /** @@ -74,5 +75,6 @@ module.exports = { }, searches: { [jobStatus.key]: jobStatus, + [hashLookup.key]: hashLookup, }, }; diff --git a/packages/verae-zapier/searches/hash_lookup.js b/packages/verae-zapier/searches/hash_lookup.js new file mode 100644 index 0000000..cc60ac4 --- /dev/null +++ b/packages/verae-zapier/searches/hash_lookup.js @@ -0,0 +1,34 @@ +const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100'; + +const perform = async (z, bundle) => { + try { + const response = await z.request({ + method: 'GET', + url: `${base()}/zapier/v1/hashes/${encodeURIComponent(bundle.inputData.sha256)}`, + }); + return [response.data]; + } catch (err) { + if (err.status === 404) return []; + throw err; + } +}; + +module.exports = { + key: 'hash_lookup', + noun: 'Timestamp', + display: { + label: 'Find Timestamp by SHA256', + description: 'Looks up an existing timestamp for a SHA256 hex digest (mock/middleware).', + }, + operation: { + inputFields: [ + { key: 'sha256', label: 'SHA256', type: 'string', required: true }, + ], + perform, + sample: { + sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', + exists: true, + jobId: '550e8400-e29b-41d4-a716-446655440000', + }, + }, +}; diff --git a/packages/verae-zapier/test/app.test.js b/packages/verae-zapier/test/app.test.js index fd712f2..558d738 100644 --- a/packages/verae-zapier/test/app.test.js +++ b/packages/verae-zapier/test/app.test.js @@ -27,6 +27,7 @@ describe('verae-zapier app definition', () => { assert.ok(App.creates.verify_timestamp); assert.ok(App.creates.batch_timestamp); assert.ok(App.searches.job_status); + assert.ok(App.searches.hash_lookup); assert.ok(App.triggers.timestamp_completed); }); diff --git a/packages/zappier/openapi.yaml b/packages/zappier/openapi.yaml index 784da3b..d6cddab 100644 --- a/packages/zappier/openapi.yaml +++ b/packages/zappier/openapi.yaml @@ -58,6 +58,41 @@ paths: responses: '200': description: OK + /v1/timestamp: + post: + operationId: timestamp + summary: Register a timestamp (proxies middleware or in-process mock) + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + data: + type: string + sha256: + type: string + hashAlg: + type: string + responses: + '202': + description: Accepted + /v1/hashes/{sha256}: + get: + operationId: hash-lookup + summary: Lookup a SHA256 timestamp (mock) + parameters: + - name: sha256 + in: path + required: true + schema: + type: string + responses: + '200': + description: OK + '404': + description: Missing /v1/add: post: operationId: add diff --git a/packages/zappier/src/app.ts b/packages/zappier/src/app.ts index 3a2286e..9a5c5a1 100644 --- a/packages/zappier/src/app.ts +++ b/packages/zappier/src/app.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { randomUUID } from 'crypto'; +import { createHash, randomUUID } from 'crypto'; import express, { Express, NextFunction, Request, RequestHandler, Response } from 'express'; import * as OpenApiValidator from 'express-openapi-validator'; import swaggerUi from 'swagger-ui-express'; @@ -97,6 +97,10 @@ export function buildApp(deps: AppDeps = {}): { }, }; const items: StoredItem[] = []; + const hashIndex = new Map< + string, + { jobId: string; sha256: string; data?: string; timestamp: string } + >(); const app = express(); app.use(express.json()); @@ -157,6 +161,36 @@ export function buildApp(deps: AppDeps = {}): { res.json({ output: text.toUpperCase(), quote: res.locals.quote }); }); + app.post('/v1/timestamp', meter('timestamp', usage, pricing), (req, res) => { + const data = req.body?.data != null ? String(req.body.data) : ''; + const sha256 = + (req.body?.sha256 && String(req.body.sha256).toLowerCase()) || + (data ? createHash('sha256').update(data, 'utf8').digest('hex') : ''); + if (!sha256) { + res.status(400).json({ error: 'data or sha256 is required' }); + return; + } + const existing = hashIndex.get(sha256); + if (existing) { + res.status(202).json({ jobId: existing.jobId, sha256, existing: true, timestamp: existing.timestamp }); + return; + } + const jobId = randomUUID(); + const timestamp = new Date().toISOString(); + hashIndex.set(sha256, { jobId, sha256, data, timestamp }); + res.status(202).json({ jobId, sha256, existing: false, timestamp }); + }); + + app.get('/v1/hashes/:sha256', meter('hash-lookup', usage, pricing), (req, res) => { + const sha256 = String(req.params.sha256 || '').toLowerCase(); + const rec = hashIndex.get(sha256); + if (!rec) { + res.status(404).json({ error: 'Hash not found' }); + return; + } + res.json({ exists: true, ...rec }); + }); + app.post('/v1/add', meter('add', usage, pricing), (req, res) => { const number1 = Number(req.body?.number1); const number2 = Number(req.body?.number2); diff --git a/packages/zappier/src/pricing.ts b/packages/zappier/src/pricing.ts index 643c790..5512e56 100644 --- a/packages/zappier/src/pricing.ts +++ b/packages/zappier/src/pricing.ts @@ -64,6 +64,8 @@ export const DEFAULT_RATE_CARD: RateCard = { 'storage-list': { kind: 'free' }, transform: { kind: 'fixed', fixedCents: 4 }, add: { kind: 'free' }, + timestamp: { kind: 'fixed', fixedCents: 4 }, + 'hash-lookup': { kind: 'free' }, storage: { kind: 'variable', baseCents: 10, perKbCents: 1, perMbCents: 50 }, }, }; diff --git a/packages/zappier/tests/app.test.ts b/packages/zappier/tests/app.test.ts index e1546f3..e6cef14 100644 --- a/packages/zappier/tests/app.test.ts +++ b/packages/zappier/tests/app.test.ts @@ -18,6 +18,26 @@ describe('Zappier API', () => { expect(res.body.quote.totalCents).toBe(0); }); + it('POST /v1/timestamp is idempotent by sha256', async () => { + const { app } = buildApp(); + const a = await request(app) + .post('/v1/timestamp') + .set('x-api-key', KEY) + .send({ data: 'abc' }); + expect(a.status).toBe(202); + const b = await request(app) + .post('/v1/timestamp') + .set('x-api-key', KEY) + .send({ data: 'abc' }); + expect(b.body.jobId).toBe(a.body.jobId); + expect(b.body.existing).toBe(true); + const look = await request(app) + .get(`/v1/hashes/${a.body.sha256}`) + .set('x-api-key', KEY); + expect(look.status).toBe(200); + expect(look.body.jobId).toBe(a.body.jobId); + }); + it('POST /v1/add returns the sum and is free', async () => { const { app } = buildApp(); const res = await request(app)