Fix catalog book links and rewrite PDF destinations to live HTTPS.
Some checks are pending
offline / test (push) Waiting to run

Module and model book cards now point at generated index.html/index.pdf
instead of directory URLs that 404. WeasyPrint file:// annotations are
mapped to https://zapier.georgelambert.org/ paths that exist. Developer
module, architecture, and research snapshot links target published files.
This commit is contained in:
George Lambert 2026-09-11 22:50:00 -04:00
parent 666eca79a1
commit 85b5b53ea9
26 changed files with 2253 additions and 35 deletions

View file

@ -30,9 +30,9 @@
| Doc | Purpose | | Doc | Purpose |
|-----|---------| |-----|---------|
| [README.md](README.md) | Repo overview and quick start | | [README.md](README.md) | Repo overview and quick start |
| [docs/architecture/overview.md](docs/architecture/overview.md) | System diagram and data flows | | [docs/02-architecture/overview.md](docs/02-architecture/overview.md) | System diagram and data flows |
| [docs/architecture/nats-subjects.md](docs/architecture/nats-subjects.md) | Subject topology and payloads | | [docs/02-architecture/nats-subjects.md](docs/02-architecture/nats-subjects.md) | Subject topology and payloads |
| [docs/developer/modules/](docs/developer/modules/) | Per-module function reference | | [docs/modules/README.md](docs/modules/README.md) | Per-module function reference |
| [docs/plans/phase-gates.md](docs/plans/phase-gates.md) | Gate commands and acceptance criteria | | [docs/plans/phase-gates.md](docs/plans/phase-gates.md) | Gate commands and acceptance criteria |
| [docs/api/middleware-openapi.yaml](docs/api/middleware-openapi.yaml) | Zapier-facing OpenAPI | | [docs/api/middleware-openapi.yaml](docs/api/middleware-openapi.yaml) | Zapier-facing OpenAPI |

View file

@ -0,0 +1,180 @@
# Zapier function reference (CLI, core, SDK)
**Grok start:** `db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" })`
MCP tools: [MCP-REFERENCE.md](MCP-REFERENCE.md). This file is everything else.
Each function has a Mongo document with the same sections as MCP: high-level, internals, typed inputs, outputs, related, twins, examples.
## Core (`kind: "core_function"`)
| Function | Signature | Category |
|----------|-----------|----------|
| `AfterResponseMiddleware` | `(response, z, bundle) => response | Promise<response>` | middleware |
| `BeforeRequestMiddleware` | `(request, z, bundle) => request | Promise<request>` | middleware |
| `CreatePerform` | `(z, bundle) => object | Promise<object>` | perform |
| `PollingTriggerPerform` | `(z, bundle) => object[] | Promise<object[]>` | perform |
| `SearchPerform` | `(z, bundle) => object[] | Promise<object[]>` | perform |
| `WebhookTriggerPerform` | `(z, bundle) => object[] | Promise<object[]>` | perform |
| `createAppTester` | `createAppTester(appRaw, options?) → (func|request, bundle?) => Promise` | testing |
| `performBuffer` | `(z, BufferedBundle) => Promise<{[id]: {outputData?, error?}}>` | perform |
| `z.JSON.parse` | `z.JSON.parse(text) → any` | json |
| `z.JSON.stringify` | `z.JSON.stringify(value) → string` | json |
| `z.cache.delete` | `z.cache.delete(key) → Promise<boolean>` | cache |
| `z.cache.get` | `z.cache.get(key) → Promise<any>` | cache |
| `z.cache.set` | `z.cache.set(key, value, ttl?, scope?, nx?) → Promise<boolean|null>` | cache |
| `z.console` | `z.console.log|info|warn|error(...args) → void` | debug |
| `z.cursor.get` | `z.cursor.get() → Promise<string>` | polling |
| `z.cursor.set` | `z.cursor.set(cursor) → Promise<null>` | polling |
| `z.dehydrate` | `z.dehydrate(func, inputData?, cacheExpiration?) → string` | hydration |
| `z.dehydrateFile` | `z.dehydrateFile(func, inputData?, cacheExpiration?) → string` | hydration |
| `z.errors.CheckError` | `throw new z.errors.CheckError(message?)` | errors |
| `z.errors.DehydrateError` | `throw new z.errors.DehydrateError(message?)` | errors |
| `z.errors.Error` | `throw new z.errors.Error(message, code?, status?)` | errors |
| `z.errors.ExpiredAuthError` | `throw new z.errors.ExpiredAuthError(message?)` | errors |
| `z.errors.HaltedError` | `throw new z.errors.HaltedError(message?)` | errors |
| `z.errors.RefreshAuthError` | `throw new z.errors.RefreshAuthError(message?)` | errors |
| `z.errors.ResponseError` | `throw new z.errors.ResponseError(response)` | errors |
| `z.errors.StopRequestError` | `throw new z.errors.StopRequestError(message?)` | errors |
| `z.errors.ThrottledError` | `throw new z.errors.ThrottledError(message, delaySeconds?)` | errors |
| `z.generateCallbackUrl` | `z.generateCallbackUrl() → string` | callback |
| `z.hash` | `z.hash(algorithm, data, encoding?, input_encoding?) → string` | crypto |
| `z.request` | `z.request(url, options?) → Promise<HttpResponse>` | http |
| `z.stashFile` | `z.stashFile(input, knownLength?, filename?, contentType?) → string` | files |
| `zapier.tools.env.inject` | `zapier.tools.env.inject(filename?)` | testing |
## Platform CLI (`kind: "cli_function"`)
| Command | Usage |
|---------|-------|
| `analytics` | `zapier-platform analytics` |
| `build` | `zapier-platform build` |
| `canary:create` | `zapier-platform canary:create VERSIONFROM VERSIONTO` |
| `canary:delete` | `zapier-platform canary:delete VERSIONFROM VERSIONTO` |
| `canary:list` | `zapier-platform canary:list` |
| `convert` | `zapier-platform convert PATH` |
| `delete:integration` | `zapier-platform delete:integration` |
| `delete:version` | `zapier-platform delete:version VERSION` |
| `deprecate` | `zapier-platform deprecate VERSION DATE` |
| `describe` | `zapier-platform describe` |
| `env:get` | `zapier-platform env:get VERSION` |
| `env:set` | `zapier-platform env:set VERSION [KEY-VALUE PAIRS...]` |
| `env:unset` | `zapier-platform env:unset VERSION [KEYS...]` |
| `history` | `zapier-platform history` |
| `init` | `zapier-platform init PATH` |
| `integrations` | `zapier-platform integrations` |
| `invoke` | `zapier-platform invoke [ACTIONTYPE] [ACTIONKEY] [AUTHDATA]` |
| `jobs` | `zapier-platform jobs` |
| `legacy` | `zapier-platform legacy VERSION` |
| `link` | `zapier-platform link` |
| `login` | `zapier-platform login` |
| `logout` | `zapier-platform logout` |
| `logs` | `zapier-platform logs` |
| `migrate` | `zapier-platform migrate FROMVERSION TOVERSION [PERCENT]` |
| `promote` | `zapier-platform promote VERSION` |
| `pull` | `zapier-platform pull` |
| `push` | `zapier-platform push` |
| `register` | `zapier-platform register [TITLE]` |
| `scaffold` | `zapier-platform scaffold ACTIONTYPE NOUN` |
| `team:add` | `zapier-platform team:add EMAIL ROLE [MESSAGE]` |
| `team:get` | `zapier-platform team:get` |
| `team:remove` | `zapier-platform team:remove` |
| `test` | `zapier-platform test` |
| `upload` | `zapier-platform upload` |
| `users:add` | `zapier-platform users:add EMAIL [VERSION]` |
| `users:get` | `zapier-platform users:get` |
| `users:links` | `zapier-platform users:links` |
| `users:remove` | `zapier-platform users:remove EMAIL` |
| `validate` | `zapier-platform validate` |
| `versions` | `zapier-platform versions` |
## SDK (`kind: "sdk_function"`)
| CLI | TypeScript | Category | MCP twin |
|-----|------------|----------|----------|
| `get-profile` | `zapier.getProfile` | Accounts | — |
| `login` | `zapier.login` | Accounts | — |
| `logout` | `zapier.logout` | Accounts | — |
| `signup` | `zapier.signup` | Accounts | — |
| `get-action` | `zapier.getAction` | Actions | inspect_zapier_actions |
| `get-action-input-fields-schema` | `zapier.getActionInputFieldsSchema` | Actions | inspect_zapier_actions |
| `list-action-input-field-choices` | `zapier.listActionInputFieldChoices` | Actions | inspect_zapier_actions (enum_property) |
| `list-action-input-fields` | `zapier.listActionInputFields` | Actions | inspect_zapier_actions |
| `list-actions` | `zapier.listActions` | Actions | inspect_zapier_actions / discover_zapier_actions |
| `run-action` | `zapier.runAction` | Actions | execute_zapier_read_action / execute_zapier_write_action |
| `get-app` | `zapier.getApp` | Apps | discover_zapier_actions |
| `list-apps` | `zapier.listApps` | Apps | discover_zapier_actions |
| `create-client-credentials` | `zapier.createClientCredentials` | Client Credentials | — |
| `delete-client-credentials` | `zapier.deleteClientCredentials` | Client Credentials | — |
| `list-client-credentials` | `zapier.listClientCredentials` | Client Credentials | — |
| `cancel-durable-run` | `zapier.cancelDurableRun` | Code Workflows | — |
| `create-workflow` | `zapier.createWorkflow` | Code Workflows | — |
| `delete-workflow` | `zapier.deleteWorkflow` | Code Workflows | — |
| `disable-workflow` | `zapier.disableWorkflow` | Code Workflows | — |
| `enable-workflow` | `zapier.enableWorkflow` | Code Workflows | — |
| `get-durable-run` | `zapier.getDurableRun` | Code Workflows | — |
| `get-trigger-run` | `zapier.getTriggerRun` | Code Workflows | — |
| `get-workflow` | `zapier.getWorkflow` | Code Workflows | — |
| `get-workflow-run` | `zapier.getWorkflowRun` | Code Workflows | — |
| `get-workflow-version` | `zapier.getWorkflowVersion` | Code Workflows | — |
| `list-durable-runs` | `zapier.listDurableRuns` | Code Workflows | — |
| `list-workflow-runs` | `zapier.listWorkflowRuns` | Code Workflows | — |
| `list-workflow-versions` | `zapier.listWorkflowVersions` | Code Workflows | — |
| `list-workflows` | `zapier.listWorkflows` | Code Workflows | — |
| `publish-workflow-version` | `zapier.publishWorkflowVersion` | Code Workflows | — |
| `run-durable` | `zapier.runDurable` | Code Workflows | — |
| `trigger-workflow` | `zapier.triggerWorkflow` | Code Workflows | — |
| `update-workflow` | `zapier.updateWorkflow` | Code Workflows | — |
| `create-connection` | `zapier.createConnection` | Connections | manage_zapier_connections |
| `find-first-connection` | `zapier.findFirstConnection` | Connections | list_zapier_connections |
| `find-unique-connection` | `zapier.findUniqueConnection` | Connections | list_zapier_connections |
| `get-connection` | `zapier.getConnection` | Connections | — |
| `get-connection-start-url` | `zapier.getConnectionStartUrl` | Connections | manage_zapier_connections |
| `list-connections` | `zapier.listConnections` | Connections | list_zapier_connections |
| `wait-for-new-connection` | `zapier.waitForNewConnection` | Connections | manage_zapier_connections (after auth_url) |
| `curl` | `zapier.curl` | HTTP Requests | write_code_action / z.request analog |
| `create-table` | `zapier.createTable` | Tables | — |
| `create-table-fields` | `zapier.createTableFields` | Tables | — |
| `create-table-records` | `zapier.createTableRecords` | Tables | — |
| `delete-table` | `zapier.deleteTable` | Tables | — |
| `delete-table-fields` | `zapier.deleteTableFields` | Tables | — |
| `delete-table-records` | `zapier.deleteTableRecords` | Tables | — |
| `get-table` | `zapier.getTable` | Tables | — |
| `get-table-record` | `zapier.getTableRecord` | Tables | — |
| `list-table-fields` | `zapier.listTableFields` | Tables | — |
| `list-table-records` | `zapier.listTableRecords` | Tables | — |
| `list-tables` | `zapier.listTables` | Tables | — |
| `update-table-records` | `zapier.updateTableRecords` | Tables | — |
| `ack-trigger-inbox-messages` | `zapier.ackTriggerInboxMessages` | Triggers | — |
| `create-trigger-inbox` | `zapier.createTriggerInbox` | Triggers | — |
| `delete-trigger-inbox` | `zapier.deleteTriggerInbox` | Triggers | — |
| `drain-trigger-inbox` | `zapier.drainTriggerInbox` | Triggers | — |
| `ensure-trigger-inbox` | `zapier.ensureTriggerInbox` | Triggers | — |
| `get-trigger-inbox` | `zapier.getTriggerInbox` | Triggers | — |
| `get-trigger-input-fields-schema` | `zapier.getTriggerInputFieldsSchema` | Triggers | — |
| `lease-trigger-inbox-messages` | `zapier.leaseTriggerInboxMessages` | Triggers | — |
| `list-trigger-inbox-messages` | `zapier.listTriggerInboxMessages` | Triggers | — |
| `list-trigger-inboxes` | `zapier.listTriggerInboxes` | Triggers | — |
| `list-trigger-input-field-choices` | `zapier.listTriggerInputFieldChoices` | Triggers | — |
| `list-trigger-input-fields` | `zapier.listTriggerInputFields` | Triggers | — |
| `list-triggers` | `zapier.listTriggers` | Triggers | — |
| `pause-trigger-inbox` | `zapier.pauseTriggerInbox` | Triggers | — |
| `release-trigger-inbox-messages` | `zapier.releaseTriggerInboxMessages` | Triggers | — |
| `resume-trigger-inbox` | `zapier.resumeTriggerInbox` | Triggers | — |
| `update-trigger-inbox` | `zapier.updateTriggerInbox` | Triggers | — |
| `watch-trigger-inbox` | `zapier.watchTriggerInbox` | Triggers | — |
| `add` | `zapier.add` | Utilities | — |
| `build-manifest` | `zapier.buildManifest` | Utilities | — |
| `feedback` | `zapier.feedback` | Utilities | send_feedback |
| `generate-app-types` | `zapier.generateAppTypes` | Utilities | — |
| `get-login-config-path` | `zapier.getLoginConfigPath` | Utilities | — |
| `init` | `zapier.init` | Utilities | — |
| `mcp` | `zapier.mcp` | Utilities | hosted Zapier MCP (different server) |
## Query
```js
db.platform_reference.findOne({ kind: "guide", key: "zapier-functions" })
db.platform_reference.find({ kind: "core_function", key: "z.request" })
db.platform_reference.find({ kind: "sdk_function", "meta.category": "Actions" })
```

24
docs/00-sources/LOGIN.md Normal file
View file

@ -0,0 +1,24 @@
# Zapier logins (you must finish these in a browser)
CLIs are installed. They cannot push, list your apps, or run live actions until you authenticate.
```bash
source scripts/dev-env.sh
# Publish integrations (deploy key → ~/.zapierrc)
zapier-platform login
# SSO-only account:
# zapier-platform login --sso
# Consume 9k apps from code
zapier-sdk login
# Then from scratch/oauth2-typescript:
cd scratch/oauth2-typescript
zapier-platform register "My OAuth2 Lab"
zapier-platform push
```
Hosted MCP (optional, for in-chat `discover_zapier_actions`): add this client connector to `https://mcp.zapier.com/api/v1/connect` (OAuth). Mongo MCP is already in `~/.grok/config.toml`; restart Grok so this session picks it up, and keep `./scripts/mongo-tunnel.sh` running.
Do not commit `~/.zapierrc` or SDK credentials.

View file

@ -0,0 +1,362 @@
# Zapier MCP function reference
**Grok start:** `db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" })`
or [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md). This file is the MCP branch.
Mongo: `db.platform_reference.find({ kind: "mcp_function" })` — one document per tool, with JSON Schema, typed inputs, outputs, internals, related tools, and code.
Hosted server: `https://mcp.zapier.com/api/v1/connect` (closed source, Streamable HTTP).
Plugin repo only: `repos/zapier-mcp` (skills + manifests).
Live input schemas captured in `raw/site-extras/zapier-mcp-tools-gumloop.json`.
Official docs still list **14** meta-tools. Live servers expose **17**: the 14 plus `list_zapier_connections`, `manage_zapier_connections`, and rolling-out `write_code_action`.
---
## Call graph
```
auto_provision_mcp (also runs on OAuth connect)
discover_zapier_actions ──► enable_zapier_action ──► manage_zapier_connections
│ │
└──────────────► inspect_zapier_actions ◄── list_zapier_connections
(repeat for enums / dynamic fields)
┌───────────────┴───────────────┐
▼ ▼
execute_zapier_read_action execute_zapier_write_action
│ │
└──────── write_code_action ────┘
Skills: list_zapier_skills → get_zapier_skill("zapier:onboarding")
create_zapier_skill / update_zapier_skill / delete_zapier_skill
Config UI: get_configuration_url
Feedback: send_feedback
```
Safety: reads run free; writes need explicit user approval after you show the payload.
Billing: each **successful** execute costs **2 Zapier tasks**. Meta-tools do not. Failures are free.
Never invent `selected_api` (Gmail is `GoogleMailV2CLIAPI`, not `GmailCLIAPI`) or action keys. Always `inspect_zapier_actions` first.
---
## Action management
### `discover_zapier_actions`
Search the 9,000+ app catalog for actions this server *could* enable. Call before saying an app is unavailable.
**Internals:** Catalog lookup (same directory as SDK `listApps` / `listActions`). No OAuth, no mutation. Returns `selected_api` that later tools require verbatim.
**Input**
```ts
type Input = {
app?: string; // Search by name. Omit for popular apps.
};
```
**Output (documented):** `{ apps: [{ app, selected_api, actions[] }] }`
**Related:** `enable_zapier_action`, `inspect_zapier_actions`, `manage_zapier_connections`
**SDK twin:** `zapier.listApps({ search })`, `zapier-sdk list-apps --search`
```ts
await client.callTool({
name: "discover_zapier_actions",
arguments: { app: "gmail" },
});
```
---
### `enable_zapier_action`
Add one action (or `*` / omit for all) for a catalog app to this server.
**Internals:** Writes mcp.zapier.com server config. If the user has no connection, returns `auth_url`. Does not call the partner API.
**Input**
```ts
type Input = {
selected_api: string; // e.g. "GoogleMailV2CLIAPI" from discover
app_display_name?: string; // "Gmail" — for friendly confirmations
action?: string; // key, or "*" / omit for all
};
```
**Output:** enabled action list, which execute tool to use (read vs write), optional `auth_url`.
**Related:** `discover_zapier_actions`, `inspect_zapier_actions`, `disable_zapier_action`, `manage_zapier_connections`
```ts
await client.callTool({
name: "enable_zapier_action",
arguments: {
selected_api: "GoogleMailV2CLIAPI",
app_display_name: "Gmail",
action: "find_email",
},
});
```
---
### `disable_zapier_action`
Remove one action, or every action for `selected_api`. Does not revoke the Zapier connection.
**Input**
```ts
type Input = {
selected_api: string;
app_display_name?: string;
action?: string; // omit = disable the whole app on this server
};
```
**Related:** `inspect_zapier_actions`, `enable_zapier_action`
---
### `inspect_zapier_actions`
**Call this first before every execute.** Lists enabled apps/actions and resolves dynamic fields.
**Internals:** Reads this server's enabled-action table. With `tool_name` + `enum_property` it hits the same Platform dynamic-dropdown endpoint as the visual builder. With parent `params` it returns `dynamic_properties_schema` for fields that depend on earlier answers (spreadsheet → worksheet). Does not run the partner action.
**Input**
```ts
type Input = {
selected_api?: string;
action?: string;
tool_name?: string; // collision-safe id from a prior inspect
connection_id?: number | string; // only if not using the default account
enum_property?: string; // field with is_dynamic_enum: true
enum_search?: string;
enum_cursor?: string;
params?: Record<string, unknown>; // parent values for dependent fields
};
```
**Output (documented):** apps → actions with `action`, `tool_name`, execute tool name, parameter schema (`is_dynamic_enum`, `dynamic_properties_depends_on`), `connections.default`, plus `dynamic_enum_values` / `dynamic_properties_schema` on follow-up calls.
**Related:** both `execute_*`, `discover_zapier_actions`, `list_zapier_connections`
**SDK twin:** `getActionInputFieldsSchema`, `list-action-input-field-choices`
```ts
await client.callTool({ name: "inspect_zapier_actions", arguments: {} });
await client.callTool({
name: "inspect_zapier_actions",
arguments: {
tool_name: "SlackCLIAPI.send_channel_message",
enum_property: "channel",
enum_search: "launches",
},
});
```
---
### `auto_provision_mcp`
One-shot setup from the user's **own** existing Zapier connections (not shared teammates'). Also runs automatically after OAuth connect. Returns enabled apps plus top Zap titles (skill ideas). No inputs.
**Related:** `inspect_zapier_actions`, `enable_zapier_action`
---
## Execution
### `execute_zapier_read_action`
Run a search / lookup / get. No confirmation required. **2 tasks** on success.
**Internals:** Maps to Zapier Platform search/read `perform` (`runAction({ actionType: "search"|"read" })`) using the user's default connection (or `connection_id`). `params``bundle.inputData`. Empty results = not found, not an error.
**Input**
```ts
type Input = {
selected_api: string;
action: string; // exact key from inspect — never guess
tool_name?: string; // preferred when keys collide across apps
connection_id?: number | string;
params?: Record<string, unknown>; // nest dynamic fields under dynamic_properties
};
```
**Output:** action-specific records (`results[]` or `data[]`). 401 → `manage_zapier_connections`.
**Related:** `inspect_zapier_actions`, `discover_zapier_actions`, `enable_zapier_action`
**SDK twin:** `repos/sdk/examples/by-app/*/find-*.ts`
```ts
await client.callTool({
name: "execute_zapier_read_action",
arguments: {
selected_api: "GoogleMailV2CLIAPI",
action: "find_email",
tool_name: "GoogleMailV2CLIAPI.find_email",
params: { query: "from:sarah@acme.com newer_than:7d" },
},
});
```
If the user said only a first name, do **not** take the first hit — list candidates.
---
### `execute_zapier_write_action`
Create / update / send. **Show the payload and wait for explicit approval.** **2 tasks** on success. Not rolled back.
**Input:** same shape as the read execute.
**Related:** `inspect_zapier_actions`, `execute_zapier_read_action`
**SDK twin:** `repos/sdk/examples/by-app/gmail/send-email.ts`
```ts
// 1) inspect + resolve channel enum 2) confirm with user 3) write
await client.callTool({
name: "execute_zapier_write_action",
arguments: {
selected_api: "SlackCLIAPI",
action: "send_channel_message",
tool_name: "SlackCLIAPI.send_channel_message",
params: { channel: "C01234567", text: "Release shipped, monitoring now" },
},
});
```
---
### `write_code_action` (rolling out)
Generate a sandboxed custom action when the catalog has no match. Auth is injected from the connected account — **never put secrets in `requirements`**. Same-name call overwrites.
**Internals:** Zapier generates code and runs later invokes in a sandbox with the app connection. Closest SDK analog is `zapier.fetch(url, { connection, method })`.
**Input**
```ts
type Input = {
selected_api: string;
code_action_name: string; // e.g. "list_channel_users"
requirements: string; // natural language; endpoint, filters, pagination
};
```
**Related:** `discover_zapier_actions`, `inspect_zapier_actions`
---
## Connections
### `list_zapier_connections`
List OAuth grants for one app. Default = the user's own accounts. Set `include_shared: true` only if they asked.
**Input**
```ts
type Input = {
selected_api: string;
include_shared?: boolean;
limit?: number; // 1100, default 20
cursor?: string;
};
```
**Output (documented):** `{ connections: [{ connection_id, title, owner, is_default, expired }], next_cursor? }`
**SDK twin:** `zapier.findFirstConnection`, `zapier-sdk find-first-connection`
---
### `manage_zapier_connections`
Mint an `auth_url` and/or set `default_connection_id`. An app **cannot execute** until it has a default connection. After the user finishes OAuth, list then optionally set default.
**Input**
```ts
type Input = {
selected_api: string; // verbatim from discover/inspect
app_display_name?: string;
default_connection_id?: number | string;
};
```
**SDK twin:** `zapier-sdk create-connection` / `get-connection-start-url`
---
## Skills, config, feedback
| Tool | Input | What it does |
|------|--------|----------------|
| `list_zapier_skills` | `{}` | Names + one-line descriptions. Catalog is dynamic. |
| `get_zapier_skill` | `{ name }` | Full Markdown. Packaged: `zapier:onboarding`. |
| `create_zapier_skill` | `{ name, description, skillDefinition }` | Persist Markdown. Lock IDs with `ZapierAction[app:action](param: "id", runtime)`. Resolve schemas via `inspect_zapier_actions` (tool text still says `list_enabled_zapier_actions`). |
| `update_zapier_skill` | `{ name, description?, skillDefinition? }` | Patch. |
| `delete_zapier_skill` | `{ name }` | Permanent. |
| `get_configuration_url` | `{}` | Dashboard URL for this server. |
| `send_feedback` | `{ feedback: string(12000), feedback_positive: boolean }` | Product inbox. |
Official first-run prompt:
```text
Run the Zapier onboarding skill using get_zapier_skill with name "zapier:onboarding" and follow its instructions.
```
---
## Client connection (TypeScript)
```ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.zapier.com/api/v1/connect"),
{ requestInit: { headers: { Authorization: `Bearer ${token}` } } },
);
const client = new Client({ name: "app", version: "1.0.0" });
await client.connect(transport);
const tools = await client.listTools();
```
Listed clients use OAuth to that same URL and never see the token.
---
## Plugin skills (not server tools)
In `repos/zapier-mcp/plugins/zapier/skills/`:
| Skill | When |
|-------|------|
| `zapier-onboard` | First connect / “what is Zapier MCP” |
| `zapier-demo` | One read action, live |
| `zapier-explore` | Role-tailored toolkit |
| `zapier-status` | Health / audit / diagnose |
Lifecycle rules (`zapier-lifecycle.mdc`): prefer native app MCP over Zapier when both exist; never call both.
---
## Reload
```bash
# refresh live schemas (optional)
# then:
source ~/.mcp-env
.venv/bin/python scripts/ingest-mcp-reference.py
```

80
docs/00-sources/MONGO.md Normal file
View file

@ -0,0 +1,80 @@
# Zapier vendor dataset on NS1 MongoDB
Canonical store: **MongoDB 7 in Docker on `70.88.205.138` (NS1)**, bound to **127.0.0.1:27017 only**.
Database: `zapier`
Collections: `apps` (9,986), `templates` (332,441), `help_articles` (1,272), **`platform_reference` (1,489 — CLI, `z.*` functions, schema, official docs, example apps)**
Meta: `meta.ingest`
## Connect (always through SSH)
```bash
# keep this running
./scripts/mongo-tunnel.sh
# or: ssh -N -L 27017:127.0.0.1:27017 ns1
```
Then on this Mac:
| Tool | Connection |
|------|------------|
| mongosh | `source ~/.mcp-env && mongosh "$ZAPIER_MONGO_URI"` |
| Compass / Cursor MongoDB plugin | `mongodb://127.0.0.1:27017` after the tunnel is up (auth: user `zapier`, authSource `admin`) |
| Grok MCP (`mongodb` plugin) | `MDB_MCP_CONNECTION_STRING` in `~/.mcp-env` and `~/.grok/config.toml` |
| Python | `.venv/bin/python scripts/load-mongodb.py` |
Do **not** point Compass at `mongodb://70.88.205.138:27017`. Port 27017 is not on the public interface.
Credentials live in `~/.mcp-env` (mode 600) and `~/.config/zapier-mongo/root.pass` on NS1. Not in git.
## Reload after a scrape
```bash
./scripts/mongo-tunnel.sh &
source ~/.mcp-env
.venv/bin/python scripts/load-mongodb.py
```
## Installed Grok / editor tools
- Grok plugins: **mongodb**, **mongodb-atlas** (xAI Official / MongoDB agent-skills)
- Grok MCP server: `mongodb-mcp-server@3` via npx
- Cursor + VS Code: **mongodb.mongodb-vscode**
- Local CLI: **mongosh** 2.9.2
- NS1: Docker container `zapier-mongo` (`mongo:7`), restart unless-stopped
## Heavy jobs run on NS1
The scrape/load workspace on the server is `/home/marchon/zapier-research` (home is on SSD3).
```bash
ssh ns1
cd /home/marchon/zapier-research
# resume scrapes, then:
export ZAPIER_MONGO_URI="mongodb://zapier:$(cat ~/.config/zapier-mongo/root.pass)@127.0.0.1:27017/zapier?authSource=admin"
.venv/bin/python scripts/load-mongodb.py
```
Laptop is for Compass/Grok/tunnel only. Do not run the 10k-site scrapes locally.
## Collections
| Collection | Documents | Contents |
|------------|----------:|----------|
| `apps` | 9,986 | Identity, contacts, Zapier controls, commercial flags, featured templates, overview |
| `templates` | 332,441 | Full public Zap recipe catalog (title, URL, app pair) |
| `help_articles` | 1,272 | Zapier help center (get-started, common problems, platform FAQ) |
| `platform_reference` | ~1,777 | Toolkit + functions + **schema_json**, **api_function** (55), **platform_news** (49), **template** (32). Start: `findOne({kind:"guide", key:"build-new-connector"})`. |
| `meta` | 1 | Last ingest timestamp |
Reload the developer collection on NS1 after recloning repos:
```bash
ssh ns1
cd /home/marchon/zapier-research
./scripts/clone-zapier-repos.sh
export ZAPIER_RESEARCH_ROOT=/home/marchon/zapier-research
export ZAPIER_MONGO_URI="mongodb://zapier:$(cat ~/.config/zapier-mongo/root.pass)@127.0.0.1:27017/zapier?authSource=admin"
.venv/bin/python scripts/ingest-platform-reference.py
```

View file

@ -0,0 +1,374 @@
# START HERE — Grok playbook for any Zapier work
**Grok must begin every Zapier task by loading this document, or the same playbook in Mongo:**
```js
db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" })
```
Then route:
| User wants… | Next |
|-------------|------|
| AI client / no-code actions across 9k apps | [MCP-REFERENCE.md](MCP-REFERENCE.md) and `kind: "mcp_function"` |
| **Any Zapier function** (CLI, `z.*`, SDK) | [FUNCTIONS-REFERENCE.md](FUNCTIONS-REFERENCE.md) and `kind: "cli_function" \| "core_function" \| "sdk_function"` (playbook: `guide/zapier-functions`) |
| Code that calls existing Zapier apps | `kind: "sdk_function"` (78 commands + `zapier.runAction` twins) |
| A new directory integration to publish | `cli_function` + `core_function` + `schema_json` + `template` · golden app `scratch/oauth2-typescript` |
| Public REST (Workflow / Actions / Inbox) | `kind: "api_function"` |
| Embed Zapier in a product | docs sections `embed`, `white-label`, `openapi` |
| Skill | `/zapier-build` (`.grok/skills/zapier-build`) |
---
# Zapier platform reference (for building connectors and applications)
Separate from the vendor catalog (`apps`, `templates`, `help_articles`). This is the **developer toolkit** Grok should read before writing a new Zapier integration, SDK client, MCP install, or embed.
**Mongo collection:** `zapier.platform_reference` on NS1 (~1,507 documents, text index `ref_text`).
**Local clones:** `repos/` (16 official GitHub repos, shallow).
**Local dump:** `raw/platform-reference.jsonl` (~10 MB).
**Official docs dump:** NS1 `raw/docs/` (409 markdown pages) plus `raw/site-extras/` and `raw/openapi/`.
Query after `./scripts/mongo-tunnel.sh` and `source ~/.mcp-env`:
```js
// mongosh "$ZAPIER_MONGO_URI"
use zapier
db.platform_reference.find({ kind: "cli_command", key: "init" })
db.platform_reference.find({ $text: { $search: "oauth2 refreshAccessToken" } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } })
db.platform_reference.find({ kind: "core_function" })
db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" })
```
Reload:
```bash
ssh ns1 'cd /home/marchon/zapier-research && \
export ZAPIER_RESEARCH_ROOT=/home/marchon/zapier-research && \
export ZAPIER_MONGO_URI="mongodb://zapier:$(cat ~/.config/zapier-mongo/root.pass)@127.0.0.1:27017/zapier?authSource=admin" && \
.venv/bin/python scripts/ingest-platform-reference.py'
```
---
## Which surface to use
| Goal | Surface | Package / repo | Collection `kind` |
|------|---------|----------------|-------------------|
| Publish an app to the Zapier directory | **Platform CLI** (or Platform UI) | `zapier-platform-cli` + `zapier-platform-core``zapier/zapier-platform` | `cli_command`, `core_function`, `schema_type`, `example_app` |
| Call existing Zapier apps from code | **Zapier SDK** | `@zapier/zapier-sdk` / `@zapier/zapier-sdk-cli``zapier/sdk` | `sdk_command`, `official_doc` section `sdk` |
| Connect an AI client with no code | **Zapier MCP** | hosted `mcp.zapier.com` + `zapier/zapier-mcp` plugins | `mcp_function` (17 tools) + [MCP-REFERENCE.md](MCP-REFERENCE.md) |
| Route “install Zapier” for an agent | **install-zapier** | `npx @zapier/install-zapier``zapier/install-zapier` | `skill`, `repo` |
| Prototype a local connector artifact | **Connectors** (not production) | `zapier/connectors` | `connector` |
| Embed Zapier in your product | Powered by Zapier / White Label | docs + OpenAPI | `official_doc` `embed` / `white-label`, `openapi` |
Do **not** mix `zapier-platform …` (build integrations) with `zapier-sdk …` (consume integrations).
Do **not** recommend retired AI Actions / NLA.
Do **not** quote “9,000+ apps” for Connectors (prototype, ~29 apps).
Start from `kind: "guide", key: "build-new-connector"` in Mongo.
---
## Collection map
| `kind` | n | What it is |
|--------|--:|------------|
| `official_doc` | 409 | Every `docs.zapier.com` page (markdown), tagged by `section` |
| `repo_doc` | 759 | Markdown from the cloned GitHub repos |
| `skill` | 63 | `SKILL.md` files (SDK, install-zapier, connectors, MCP plugins) |
| `schema_type` | 63 | `AppSchema`, `AuthenticationOAuth2ConfigSchema`, `TriggerSchema`, … |
| `cli_command` | 40 | `zapier-platform` commands (full usage, flags, examples) |
| `example_app` | 31 | Official templates under `zapier-platform/example-apps/*` |
| `connector` | 29 | Prototype connectors (Sheets, Notion, Linear, …) |
| `core_function` | 22 | `z.request`, `z.errors.*`, cache, cursor, dehydrate, … |
| `sdk_command` | 20 | `zapier-sdk` / SDK CLI: `list-apps`, `run-action`, … |
| `site_extra` | 22 | Leftover public zapier.com / developer surfaces + llms.txt |
| `repo` | 16 | Cloned repo index + README |
| `core_type` | 9 | `Bundle`, `ZObject`, perform signatures, middleware |
| `openapi` | 5 | Actions, Connections, Trigger Inbox, Promotions, Workflow API schema |
| `mcp_function` | 17 | Hosted Zapier MCP meta-tools (typed I/O, internals, examples) |
| `cli_function` | 40 | `zapier-platform` commands (MCP-style: typed flags, internals, examples) |
| `sdk_function` | 78 | `zapier-sdk` CLI + `zapier.camelCase` TypeScript twins |
| `api_function` | 55 | Workflow / Actions / Connections / Trigger Inbox / Promotions endpoints |
| `platform_news` | 49 | Platform CLI/core changelog 20252026 |
| `template` | 32 | Full example-app sources + golden `scratch/oauth2-typescript` |
| `schema_json` | 2 | `exported-schema.json` + official `definition.json` |
| `guide` | 4 | **Start:** `build-new-connector`. Then `zapier-mcp`, `zapier-functions`, `coding-set` |
`official_doc.section` breakdown: integration-builder 200, embed 78, api-reference 53, mcp 44, white-label 21, sdk 10, plus install/connectors.
---
## Platform CLI (`zapier-platform`)
Install: `npm install -g zapier-platform-cli`
Deprecated alias: `zapier` (same commands). Prefer `zapier-platform`.
### Scaffold → ship
```bash
zapier-platform login
zapier-platform init my-app --template oauth2 --language typescript
cd my-app && npm install
zapier-platform scaffold trigger contact
zapier-platform scaffold create contact
zapier-platform validate
zapier-platform invoke auth start
zapier-platform invoke auth test
zapier-platform invoke trigger new_contact
zapier-platform test
zapier-platform register "My App"
zapier-platform push
zapier-platform promote 1.0.0
```
`init --template` values: `basic-auth`, `callback`, `custom-auth`, `digest-auth`, `dynamic-dropdown`, `files`, `line-items`, `minimal`, `oauth1-trello`, `oauth2`, `openai`, `search-or-create`, `session-auth`. Add `--language typescript` and/or `--module esm`.
### Full command list
| Command | Usage | When |
|---------|-------|------|
| `login` / `logout` | `zapier-platform login` | Auth the CLI to developer.zapier.com |
| `init` | `init PATH` | New project from a template |
| `convert` | `convert PATH` | Visual Builder → CLI (`-i` integration id, `-v` version) |
| `scaffold` | `scaffold ACTIONTYPE NOUN` | Add `trigger\|search\|create\|resource` |
| `link` | `link` | Attach cwd to an existing integration |
| `register` | `register [TITLE]` | Create the integration on Zapier |
| `validate` | `validate` | Schema + style checks (gates push) |
| `test` | `test` | Jest via platform test runner |
| `invoke` | `invoke [TYPE] [KEY]` | Local / relay (`-a` auth id) / remote (`-r`) |
| `describe` | `describe` | Print triggers/searches/creates as Zapier sees them |
| `build` | `build` | `build/build.zip` + `source.zip` |
| `upload` | `upload` | Upload a zip without full push |
| `push` | `push` | Build + upload the `package.json` version (versions must be sequential) |
| `pull` | `pull` | Pull remote definition |
| `versions` | `versions` | List deployed versions |
| `promote` | `promote VERSION` | Make this the public default |
| `migrate` | `migrate FROM TO [PERCENT]` | Move users (same major) |
| `deprecate` | `deprecate VERSION DATE` | DATE ≥ 3 weeks out; users emailed at T-14d |
| `canary:create` | `canary:create FROM TO -p PCT -d SECS` | Temporary traffic split |
| `canary:list` / `canary:delete` | | Inspect / cancel canary |
| `env:get` / `env:set` / `env:unset` | `env:set 1.0.0 KEY=val` | Per-version secrets (`CLIENT_ID`, …) |
| `logs` | `logs` | HTTP / console logs |
| `history` | `history` | Audit trail |
| `integrations` (`apps`) | | Integrations you admin |
| `team:add` / `get` / `remove` | roles: `admin`, `collaborator`, `subscriber` | |
| `users:add` / `get` / `links` / `remove` | Invite testers to a private version | |
| `delete:version` / `delete:integration` | Only if no users/Zaps | |
| `jobs` | Background job status | |
| `legacy` | Legacy Web Builder helpers | |
| `analytics` | CLI telemetry opt-in/out | |
Full flags and examples live in `kind: "cli_command"`. Source: `repos/zapier-platform/packages/cli/docs/cli.md`.
`invoke` modes: local (default, `.env` / `authData_*`), relay (`-a <authentication-id>`), remote (`-r`). Local limitations: no hook subscribe, hydration, file upload, buffered creates, search-or-create.
---
## Runtime functions (`z`, first argument to every `perform`)
Every trigger/action/search is `(z: ZObject, bundle: Bundle) => …`.
| Function | Signature | Notes |
|----------|-----------|--------|
| `z.request` | `(url, options?) → Promise<HttpResponse>` | **Always use this**, not axios/fetch. Auth middleware + logs. `json`, `form`, `params`, `raw`, `skipThrowForStatus`. |
| `z.console.*` | `log\|info\|warn\|error` | Shows up in `zapier-platform logs` |
| `z.dehydrate` | `(func, inputData?, ttl?) → string` | Lazy object pointer |
| `z.dehydrateFile` | same | Lazy file |
| `z.stashFile` | `(buf\|stream\|url, …) → string` | Public short-lived file URL |
| `z.cursor.get` / `set` | polling cursor | Persist last-seen id/ts |
| `z.generateCallbackUrl` | `() → string` | Resume long creates (`performResume`) |
| `z.hash` | `(alg, data, enc?, inEnc?)` | Usually `sha256` |
| `z.JSON.parse` / `stringify` | | Parse throws a clean user error |
| `z.cache.get/set/delete` | per-auth JSON cache | `set(key, val, ttl?, scope?, nx?)` |
| `z.errors.Error` | `(message, code?, status?)` | User-visible failure |
| `z.errors.HaltedError` | | Stop run **without** failing the Zap |
| `z.errors.RefreshAuthError` | | 401 → refresh OAuth/session and retry |
| `z.errors.ExpiredAuthError` | | Dead connection; user must reconnect |
| `z.errors.ThrottledError` | `(message, delaySeconds?)` | Retry-After |
| `z.errors.ResponseError` | `(response)` | Usually auto-thrown by `z.request` |
| `createAppTester` | from `zapier-platform-core` | Unit-test performs |
| `zapier.tools.env.inject` | load `.env` | Tests |
Return rules:
- **Trigger / search** → array of objects (search may envelope `{results, paging_token}`).
- **Create****one object** (not an array).
- Polling items need `id` (or `primary: true` output fields).
- REST Hook: `operation.type = 'hook'` plus `performSubscribe`, `performUnsubscribe`, `performList`.
`bundle` fields: `authData`, `inputData`, `inputDataRaw`, `meta` (`isLoadingSample`, `isFillingDynamicDropdown`, `isPopulatingDedupe`, `isBulkRead`, `limit`, `page`, `timezone`, `paging_token`, `withSearch`), plus hook extras `cleanedRequest`, `rawRequest`, `targetUrl`, `subscribeData`.
See `kind: "core_function"` and `kind: "core_type"`.
---
## App definition and schema
Minimum `index.js` / `src/index.ts`:
```js
module.exports = {
version: require('./package.json').version,
platformVersion: require('zapier-platform-core').version,
authentication: { /* type + config + test + connectionLabel */ },
beforeRequest: [], // (request, z, bundle) => request
afterResponse: [], // (response, z, bundle) => response
hydrators: {},
triggers: {},
searches: {},
creates: {},
resources: {},
};
```
Canonical types in `kind: "schema_type"` (generated from `zapier-platform-schema` 19.x):
- App: `AppSchema`, `AppFlagsSchema`, `VersionSchema`
- Auth: `AuthenticationSchema` + `AuthenticationOAuth2ConfigSchema`, `…SessionConfigSchema`, `…BasicConfigSchema`, `…DigestConfigSchema`, `…CustomConfigSchema`, `…OAuth1ConfigSchema`
- Operations: `TriggerSchema`, `CreateSchema`, `SearchSchema`, `SearchOrCreateSchema`, `ResourceSchema`
- HTTP: `RequestSchema`, `RedirectRequestSchema`
- Fields: `PlainInputFieldSchema`, `PlainOutputFieldSchema`, `FieldChoicesSchema`, `InputFieldGroupsSchema`
- Runtime extras: `HydratorsSchema`, `MiddlewaresSchema`, `ThrottleObjectSchema`, `LockObjectSchema`, `BufferConfigSchema`
Full generated doc: `repos/zapier-platform/packages/schema/docs/build/schema.md`.
### Auth recipes
| `authentication.type` | Example app | Mechanism |
|-----------------------|-------------|-----------|
| `oauth2` | `example-apps/oauth2` | `authorizeUrl`, `getAccessToken`, `refreshAccessToken`, `autoRefresh` |
| `oauth1` | `oauth1-trello`, `oauth1-twitter`, `oauth1-tumblr` | HMAC signed requests |
| `session` | `session-auth` | `sessionConfig.perform` exchanges creds for a token in `authData` |
| `basic` | `basic-auth` | Username/password; platform sets header |
| `digest` | `digest-auth` | Challenge-response |
| `custom` | `custom-auth` | API key / header / query; attach in `beforeRequest` |
On HTTP 401: `throw new z.errors.RefreshAuthError()` if refreshable, else `ExpiredAuthError()`.
### Example apps (clone + `init --template`)
`babel`, `basic-auth` (+ TS), `callback`, `create`, `custom-auth` (+ TS), `digest-auth` (+ TS), `dynamic-dropdown`, `files`, `github`, `line-items`, `middleware`, `minimal`, `minimal-esm`, `oauth1-trello` (+ TS), `oauth1-tumblr`, `oauth1-twitter`, `oauth2` (+ TS), `onedrive`, `openai`, `resource`, `rest-hooks`, `search`, `search-or-create`, `session-auth` (+ TS), `trigger`.
Each is `kind: "example_app"` with README + file list.
---
## Zapier SDK CLI (consume apps — different package)
`npx @zapier/zapier-sdk-cli` / `zapier-sdk`. Docs: `official_doc` keys `sdk/cli-reference`, `sdk/reference`, `sdk/quickstart`.
| Command | Purpose |
|---------|---------|
| `signup` / `login` / `logout` | Account + SDK credentials |
| `list-apps` | Discover apps at runtime (do not hardcode keys) |
| `get-app` | One app |
| `list-actions` / `get-action` | Actions for an app |
| `list-action-input-fields` / `…-schema` / `…-choices` | Input form |
| `run-action` | Execute and wait |
| `create-action-run` / `get-action-run` | Async run |
| `create-connection` / `find-first-connection` / `find-unique-connection` | User connections |
| `get-profile` | Who am I |
| `create-client-credentials` / `list-` / `delete-` | Deploy without browser login |
Source repo: `repos/sdk` (includes `skills/zapier-sdk/SKILL.md`).
---
## MCP, install, connectors
Full typed reference: [MCP-REFERENCE.md](MCP-REFERENCE.md) and `kind: "mcp_function"`.
```js
db.platform_reference.findOne({ kind: "guide", key: "zapier-mcp" })
db.platform_reference.find({ kind: "mcp_function" }).sort({ key: 1 })
```
- **MCP docs:** `section: "mcp"` (quickstart, auth, 20+ client guides, troubleshooting).
- **Install router:** `docs.zapier.com/install` + `npx @zapier/install-zapier` (`repos/install-zapier`).
- **Connectors (prototype):** `repos/connectors/apps/{algolia,alpaca,clay,dataforseo,discord,dropbox,elevenlabs,firecrawl,gitlab,google-*,harvest,heygen,linear,microsoft-*,notion,resend,runway,telegram,trello,youtube}`.
---
## Public APIs (OpenAPI)
`kind: "openapi"`:
| Key | URL |
|-----|-----|
| `actions.yaml` | Stored Actions API |
| `connections.yaml` | Connection webhooks (White Label) |
| `trigger-inbox.yaml` | Trigger Inbox |
| `promotions-openapi.yaml` | Promotions / sponsored automation |
| `workflow-api-schema.json` | Live `https://api.zapier.com/schema` (Powered by Zapier Workflow API) |
Rendered endpoint docs: `section: "api-reference"` and `section: "embed"`.
---
## Cloned GitHub repos (`repos/`)
| Repo | Role |
|------|------|
| `zapier-platform` | Canonical monorepo: CLI, core, schema, 31 example apps |
| `zapier-platform-cli` / `-core` / `-schema` | Archived standalone copies; use the monorepo |
| `sdk` | Agent-readable SDK docs + skill |
| `zapier-mcp` | Hosted MCP plugin manifests |
| `connectors` | Prototype local connectors |
| `install-zapier` | Agent installer |
| `agent-skills` / `marketplace` / `gtm-cheat-codes` | Agent skills / marketplace listing |
| `visual-builder` | Archived Platform UI tutorial |
| `resthooks` | REST Hooks spec (instant triggers) |
| `zapier-platform-example-app-{github,oauth2,minimal}` | Standalone example mirrors |
Re-clone: `ROOT=/path scripts/clone-zapier-repos.sh`.
---
## What was left on zapier.com (and what is not public)
Pulled in this pass:
- Full `docs.zapier.com` sitemap (409 pages) + `llms.txt` / `llms-full.txt` (2.5 MB)
- Root `zapier.com` sitemap developer surfaces: developer-platform, embed-tools, integrations, partner-program, MCP, Functions (Code by Zapier), custom connections, opensource, agents
- `developer.zapier.com` (shell → docs), legacy `platform.zapier.com` / v2 docs HTML
- OpenAPI specs above
Already in other collections: 9,986 apps, 332,441 templates, 1,272 help articles, commercial/API-doc/auth scrapes.
**Not available publicly (do not invent):**
- Partnership / sales emails
- Private REST schemas of the 9,986 vendor APIs
- Auth type for ~8k apps that never published help/API-doc signals
- Internal Zapier implementation IDs mapping to OAuth vs API key
---
## Grok query recipes
```js
// Build a new OAuth2 integration
db.platform_reference.find({
$or: [
{ kind: "guide" },
{ kind: "cli_command", key: { $in: ["init", "scaffold", "invoke", "push", "validate"] } },
{ kind: "example_app", key: /oauth2/ },
{ kind: "schema_type", key: /AuthenticationOAuth2|AppSchema|TriggerSchema|CreateSchema/ },
{ kind: "core_function", key: /^z\.(request|errors)/ },
{ kind: "official_doc", key: /oauth|cli-tutorial|core/ },
]
})
// Instant (REST Hook) trigger
db.platform_reference.find({
$or: [
{ key: /hook|rest-hooks/i },
{ kind: "schema_type", key: /Hook|Trigger/ },
]
})
// Embed Workflow API
db.platform_reference.find({ section: { $in: ["embed", "api-reference", "white-label"] } })
```

View file

@ -0,0 +1,62 @@
# Restart Grok for this Zapier workspace
## 1. Leave the TUI
In Grok type:
```
/quit
```
(` /exit ` is the same.) Do **not** use `/new` — that throws away this session.
## 2. Run the restart script from a normal shell
```bash
cd /Users/marchon/research/zapier
./scripts/restart-grok.sh
```
That script:
1. `cd`s to this repo
2. Puts `zapier-platform` / `zapier-sdk` on `PATH`
3. Loads `~/.mcp-env` (Mongo URI)
4. Starts `scripts/mongo-tunnel.sh` if `127.0.0.1:27017` is down
5. Runs `grok --resume` (latest session for this directory)
## 3. After Grok opens
```
/mcps
```
Press `r` to refresh. Confirm **mongodb** is connected.
Then continue:
```
/zapier-build
```
or ask Grok to load:
```js
db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" })
```
## Other scripts
| Script | What it does |
|--------|----------------|
| `scripts/restart-grok.sh` | Tunnel + `grok --resume` |
| `scripts/ensure-mongo-tunnel.sh` | Start tunnel only |
| `scripts/mongo-tunnel.sh` | Raw `ssh -N -L 27017:… ns1` (foreground) |
| `scripts/dev-env.sh` | `source` this for PATH + Mongo URI |
| `scripts/zapier-status.sh` | Check tunnel, CLIs, login, Mongo count |
```bash
./scripts/zapier-status.sh
```
Browser logins (optional, for `push` / live SDK): see [LOGIN.md](LOGIN.md).

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="680"
viewBox="0 0 1100 680" role="img" aria-label="High-level architecture: Zapier to Verae via middleware">
<title>High-level architecture: Zapier to Verae via middleware</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">HIGH-LEVEL ARCHITECTURE</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Verae Time x Zapier</text>
<text x="24" y="70" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#64748B" text-anchor="start">Zapier never calls api.veraetime.net and never speaks NATS. HTTPS only at the public edge.</text>
<rect x="20" y="90" width="1060" height="150" rx="12" fill="#FFF1E6" fill-opacity="1" stroke="#F0D2B8" stroke-width="1.2"/>
<text x="36" y="112" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#C2410C" text-anchor="start">PUBLIC · Zapier cloud</text>
<rect x="20" y="258" width="700" height="280" rx="12" fill="#E8F1F8" fill-opacity="1" stroke="#C5D6E8" stroke-width="1.2"/>
<text x="36" y="280" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">YOUR INFRASTRUCTURE · public HTTPS edge + private workers</text>
<rect x="740" y="258" width="340" height="280" rx="12" fill="#F3EDE0" fill-opacity="1" stroke="#E0D2B4" stroke-width="1.2"/>
<text x="756" y="280" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#B45309" text-anchor="start">PRIVATE NETWORK</text>
<rect x="20" y="556" width="700" height="104" rx="12" fill="#E8F6EE" fill-opacity="1" stroke="#B7DCC7" stroke-width="1.2"/>
<text x="36" y="578" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#15803D" text-anchor="start">VERAE TIMESTAMPING SERVICE</text>
<rect x="740" y="556" width="340" height="104" rx="12" fill="#FFF1E6" fill-opacity="1" stroke="#F0D2B8" stroke-width="1.2"/>
<text x="756" y="578" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#C2410C" text-anchor="start">ZAPIER REST HOOKS</text>
<rect x="40" y="128" width="150" height="88" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="115" y="158" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">User</text>
<text x="115" y="178" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">Zap editor</text>
<text x="115" y="192" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">connection form</text>
<rect x="250" y="122" width="360" height="100" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="430" y="148" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">Zapier Platform CLI app</text>
<text x="430" y="168" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">scratch/veraetime (session TS) or verae-zapier (zmw_ JS)</text>
<text x="430" y="183" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">runs on Zapier cloud when a Zap step executes</text>
<rect x="660" y="128" width="180" height="88" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#94A3B8" stroke-width="1.4"/>
<text x="750" y="158" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">Zap trigger</text>
<text x="750" y="178" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">Timestamp Completed</text>
<text x="750" y="192" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">hooks.zapier.com</text>
<line x1="190" y1="172" x2="250" y2="172" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="220.0" y="161.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">configure / run</text>
<line x1="610" y1="172" x2="660" y2="172" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="635.0" y="161.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">hook fires Zap</text>
<rect x="48" y="300" width="644" height="120" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="370" y="326" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">verae-zapier-middleware /zapier/v1/*</text>
<text x="370" y="348" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">Auth bridge · tenants · entitlements · rate limits · REST Hook store</text>
<text x="370" y="364" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">GET /health POST /auth/login GET /auth/me POST /timestamp[/wait|/batch]</text>
<text x="370" y="380" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">POST /verify GET /status/{jobId} POST/DELETE /webhooks/*</text>
<line x1="430" y1="222" x2="430" y2="300" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="438" y="265.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="start">HTTPS Bearer zmw_ / zmt_</text>
<rect x="760" y="300" width="300" height="92" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="910" y="328" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">NATS JetStream</text>
<text x="910" y="348" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">ZAPIER_JOBS · EVENTS · WEBHOOKS</text>
<text x="910" y="362" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">never on the public internet</text>
<line x1="692" y1="346" x2="760" y2="346" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="726.0" y="335.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">publish (private)</text>
<rect x="760" y="412" width="300" height="108" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="910" y="438" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">Workers</text>
<text x="910" y="458" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">job-poller jobs.watch</text>
<text x="910" y="473" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">event-router jobs.events</text>
<text x="910" y="488" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">webhook-deliver webhooks.deliver</text>
<line x1="910" y1="392" x2="910" y2="412" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="48" y="596" width="644" height="50" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="370" y="616" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">api.veraetime.net</text>
<text x="370" y="634" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="500" fill="#334155" text-anchor="middle">POST /auth/login POST /api/timestamp 202 {jobId} GET /api/status/{jobId} POST /api/verify</text>
<line x1="220" y1="420" x2="220" y2="596" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="228" y="512.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="start">sync HTTPS</text>
<line x1="820" y1="520" x2="370" y2="596" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="595.0" y="548.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">poll / create</text>
<rect x="760" y="596" width="300" height="50" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="910" y="626" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="600" fill="#0F2744" text-anchor="middle">POST hooks.zapier.com timestamp.completed</text>
<line x1="910" y1="520" x2="910" y2="596" stroke="#C2410C" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="918" y="562.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="start">HTTPS egress</text>
</svg>

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="520"
viewBox="0 0 1100 520" role="img" aria-label="Security boundaries">
<title>Security boundaries</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">SECURITY BOUNDARIES</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">What is public, what stays private</text>
<rect x="20" y="80" width="1060" height="200" rx="12" fill="#FFF1E6" fill-opacity="1" stroke="#F0D2B8" stroke-width="1.4"/>
<text x="40" y="106" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#C2410C" text-anchor="start">PUBLIC INTERNET</text>
<rect x="48" y="124" width="300" height="130" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="198" y="158" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">Zapier cloud</text>
<text x="198" y="180" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">CLI app performs</text>
<text x="198" y="194" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">z.request only</text>
<text x="198" y="208" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">no NATS, no Verae JWT</text>
<rect x="400" y="124" width="300" height="130" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="550" y="158" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">Middleware HTTPS</text>
<text x="550" y="180" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">/zapier/v1 only</text>
<text x="550" y="194" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">TLS required in prod</text>
<text x="550" y="208" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">rate limit + entitlements</text>
<rect x="752" y="124" width="300" height="130" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="902" y="158" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">Zapier hook URLs</text>
<text x="902" y="180" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">untrusted egress</text>
<text x="902" y="194" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">timeouts required</text>
<text x="902" y="208" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">SSRF allowlist = Phase 15</text>
<line x1="348" y1="189" x2="400" y2="189" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="374.0" y="178.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">HTTPS in</text>
<line x1="700" y1="189" x2="752" y2="189" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="726.0" y="178.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">HTTPS out</text>
<rect x="20" y="300" width="1060" height="196" rx="12" fill="#F3EDE0" fill-opacity="1" stroke="#E0D2B4" stroke-width="1.4"/>
<text x="40" y="326" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#B45309" text-anchor="start">PRIVATE · do not expose ports 4222 / store / TOKEN_SECRET</text>
<rect x="48" y="348" width="320" height="126" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="208" y="380" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">NATS JetStream</text>
<text x="208" y="402" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">tokenRef, not raw JWT</text>
<text x="208" y="416" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">private network + auth</text>
<text x="208" y="430" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">mTLS is Phase 15</text>
<rect x="400" y="348" width="320" height="126" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="560" y="380" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">Workers + file store</text>
<text x="560" y="402" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">STORE_PATH store.json</text>
<text x="560" y="416" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">single-node MVP</text>
<text x="560" y="430" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Postgres/Redis = Phase 15</text>
<rect x="752" y="348" width="300" height="126" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="902" y="380" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="middle">api.veraetime.net</text>
<text x="902" y="402" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Verae JWT stays here</text>
<text x="902" y="416" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">server-side login only</text>
<text x="902" y="430" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">MOCK_VERAE for tests</text>
<line x1="368" y1="411" x2="400" y2="411" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="720" y1="411" x2="752" y2="411" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="560"
viewBox="0 0 1100 560" role="img" aria-label="Two-hop authentication">
<title>Two-hop authentication</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">AUTHENTICATION</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Two hops · Zapier never sees the Verae JWT</text>
<rect x="20" y="80" width="1060" height="220" rx="12" fill="#E8F1F8" fill-opacity="1" stroke="#C5D6E8" stroke-width="1.4"/>
<text x="40" y="106" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#1A6B6B" text-anchor="start">HOP 1 · end user → middleware</text>
<rect x="48" y="126" width="220" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="158" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">User in Zapier</text>
<text x="158" y="178" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">username + password</text>
<text x="158" y="192" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">or zmw_ API key</text>
<text x="158" y="206" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">api_base_url</text>
<text x="158" y="220" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">default :3100</text>
<rect x="320" y="126" width="280" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="460" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">POST /zapier/v1/auth/login</text>
<text x="460" y="178" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">loginWithCredentials</text>
<text x="460" y="192" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">or loginWithApiKey</text>
<text x="460" y="206" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">returns accessToken</text>
<text x="460" y="220" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">stored as sessionKey (TS)</text>
<rect x="650" y="126" width="200" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="750" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">Tokens issued</text>
<text x="750" y="178" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">zmw_ tenant API key</text>
<text x="750" y="192" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">zmt_ HMAC session</text>
<text x="750" y="206" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">TOKEN_SECRET</text>
<text x="750" y="220" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">never commit</text>
<rect x="880" y="126" width="176" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="968" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">Auth test</text>
<text x="968" y="178" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">GET /auth/me</text>
<text x="968" y="192" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">tenant + plan</text>
<text x="968" y="206" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">usage</text>
<text x="968" y="220" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">401 → refresh</text>
<line x1="268" y1="200" x2="320" y2="200" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="600" y1="200" x2="650" y2="200" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="850" y1="200" x2="880" y2="200" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="20" y="320" width="1060" height="216" rx="12" fill="#E8F6EE" fill-opacity="1" stroke="#B7DCC7" stroke-width="1.4"/>
<text x="40" y="346" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#15803D" text-anchor="start">HOP 2 · middleware → Verae (server-side only)</text>
<rect x="48" y="366" width="300" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="198" y="396" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">veraeClient</text>
<text x="198" y="418" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">POST /auth/login on Verae</text>
<text x="198" y="432" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">or MOCK_VERAE=true</text>
<text x="198" y="446" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">JWT held in process</text>
<text x="198" y="460" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">redacted in DEBUG_VERAE</text>
<rect x="400" y="366" width="300" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="550" y="396" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">api.veraetime.net</text>
<text x="550" y="418" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Authorization: Bearer &lt;JWT&gt;</text>
<text x="550" y="432" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">timestamp / status / verify</text>
<text x="550" y="446" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">source of truth</text>
<text x="550" y="460" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">not reachable from Zapier</text>
<rect x="752" y="366" width="300" height="148" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#C2410C" stroke-width="1.8"/>
<text x="902" y="396" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#C2410C" text-anchor="middle">Do not leak</text>
<text x="902" y="418" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">no Verae JWT in Zapier</text>
<text x="902" y="432" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">no JWT in NATS (use tokenRef)</text>
<text x="902" y="446" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">no secrets in git</text>
<text x="902" y="460" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">no secrets in debug logs</text>
<line x1="348" y1="440" x2="400" y2="440" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="700" y1="440" x2="752" y2="440" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="620"
viewBox="0 0 1100 620" role="img" aria-label="Create Timestamp async plus REST Hook">
<title>Create Timestamp async plus REST Hook</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">FLOW A</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Create Timestamp · async job + hook</text>
<rect x="-10" y="78" width="160" height="36" rx="6" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="70" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">Zapier app</text>
<line x1="70" y1="114" x2="70" y2="590" stroke="#94A3B8" stroke-dasharray="3 5"/>
<rect x="210" y="78" width="160" height="36" rx="6" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="290" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">Middleware</text>
<line x1="290" y1="114" x2="290" y2="590" stroke="#94A3B8" stroke-dasharray="3 5"/>
<rect x="430" y="78" width="160" height="36" rx="6" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="510" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">NATS</text>
<line x1="510" y1="114" x2="510" y2="590" stroke="#94A3B8" stroke-dasharray="3 5"/>
<rect x="650" y="78" width="160" height="36" rx="6" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="730" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">Workers</text>
<line x1="730" y1="114" x2="730" y2="590" stroke="#94A3B8" stroke-dasharray="3 5"/>
<rect x="870" y="78" width="160" height="36" rx="6" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="950" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">Verae / Hooks</text>
<line x1="950" y1="114" x2="950" y2="590" stroke="#94A3B8" stroke-dasharray="3 5"/>
<line x1="70" y1="148" x2="290" y2="148" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="180.0" y="137.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">1 POST /timestamp</text>
<line x1="290" y1="190" x2="950" y2="190" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="620.0" y="179.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">2 entitlement + POST /api/timestamp</text>
<line x1="950" y1="232" x2="290" y2="232" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="620.0" y="246.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">3 202 { jobId }</text>
<line x1="290" y1="274" x2="70" y2="274" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="180.0" y="288.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">4 return jobId to Zap</text>
<line x1="290" y1="322" x2="510" y2="322" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="400.0" y="311.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">5 publish jobs.watch</text>
<line x1="510" y1="364" x2="730" y2="364" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="620.0" y="353.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">6 job-poller</text>
<line x1="730" y1="406" x2="950" y2="406" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="840.0" y="395.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">7 GET /api/status/{jobId}</text>
<line x1="950" y1="448" x2="730" y2="448" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="840.0" y="462.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">8 pending: Nak + delay</text>
<line x1="730" y1="490" x2="510" y2="490" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="620.0" y="504.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">9 terminal → jobs.events</text>
<line x1="510" y1="532" x2="730" y2="532" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="620.0" y="521.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">10 match webhooks</text>
<line x1="730" y1="574" x2="950" y2="574" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="840.0" y="563.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">11 POST timestamp.completed</text>
<text x="24" y="612" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="500" fill="#64748B" text-anchor="start">Pair this action with the Timestamp Completed hook trigger. Zapier does not poll Verae.</text>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="480"
viewBox="0 0 1100 480" role="img" aria-label="Create Timestamp and Wait">
<title>Create Timestamp and Wait</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">FLOW B</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Create Timestamp and Wait</text>
<text x="24" y="72" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#64748B" text-anchor="start">In-process path is live today. Wait-via-NATS is Phase 9.</text>
<rect x="40" y="100" width="200" height="80" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="140" y="132" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">Zapier</text>
<text x="140" y="152" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">POST /timestamp/wait</text>
<rect x="320" y="100" width="260" height="80" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="450" y="132" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">Middleware</text>
<text x="450" y="152" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">create job, then wait</text>
<line x1="240" y1="140" x2="320" y2="140" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="280.0" y="129.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">HTTPS</text>
<rect x="320" y="230" width="260" height="100" rx="8" fill="#E8F1F8" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="450" y="260" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#1A6B6B" text-anchor="middle">NATS_ENABLED=false</text>
<text x="450" y="280" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">in-process poller (Phase 6)</text>
<text x="450" y="294" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">returns StatusResponse</text>
<rect x="640" y="230" width="400" height="100" rx="8" fill="#F3EDE0" fill-opacity="1" stroke="#B45309" stroke-width="1.4"/>
<text x="840" y="260" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#B45309" text-anchor="middle">NATS_ENABLED=true · Phase 9 (open)</text>
<text x="840" y="280" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">subscribe to jobs.events</text>
<text x="840" y="294" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">hard timeout → { jobId, status: pending }</text>
<line x1="450" y1="180" x2="450" y2="230" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="580" y1="180" x2="840" y2="230" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="40" y="380" width="1020" height="72" rx="8" fill="#E8F6EE" fill-opacity="1" stroke="#B7DCC7" stroke-width="1.4"/>
<text x="550" y="410" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">Return to Zapier</text>
<text x="550" y="430" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">completed / failed status object or pending + jobId on timeout (Zapier can Find Job Status next)</text>
<line x1="450" y1="330" x2="450" y2="380" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="840" y1="330" x2="840" y2="380" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="560"
viewBox="0 0 1100 560" role="img" aria-label="NATS subject topology">
<title>NATS subject topology</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">NATS + JETSTREAM</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Private subjects, streams, and workers</text>
<rect x="40" y="88" width="250" height="250" rx="10" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<rect x="54" y="104" width="222" height="26" rx="4" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="0"/>
<text x="165.0" y="121.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="700" fill="#FFFFFF" text-anchor="middle">ZAPIER_JOBS</text>
<text x="165" y="156" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="600" fill="#134848" text-anchor="middle">verae.zapier.jobs.watch</text>
<text x="165" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Work queue</text>
<text x="165" y="200" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle"></text>
<text x="165" y="218" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">consumer: job-poller</text>
<text x="165" y="236" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">GET /api/status/{jobId}</text>
<rect x="310" y="88" width="250" height="250" rx="10" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<rect x="324" y="104" width="222" height="26" rx="4" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="0"/>
<text x="435.0" y="121.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="700" fill="#FFFFFF" text-anchor="middle">ZAPIER_EVENTS</text>
<text x="435" y="156" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="600" fill="#134848" text-anchor="middle">verae.zapier.jobs.events</text>
<text x="435" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Limits (time)</text>
<text x="435" y="200" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle"></text>
<text x="435" y="218" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">consumer: event-router</text>
<text x="435" y="236" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">enqueue webhooks / waiters</text>
<rect x="580" y="88" width="250" height="250" rx="10" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<rect x="594" y="104" width="222" height="26" rx="4" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="0"/>
<text x="705.0" y="121.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="700" fill="#FFFFFF" text-anchor="middle">ZAPIER_WEBHOOKS</text>
<text x="705" y="156" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="600" fill="#134848" text-anchor="middle">verae.zapier.webhooks.deliver</text>
<text x="705" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Work queue</text>
<text x="705" y="200" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle"></text>
<text x="705" y="218" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">consumer: webhook-deliver</text>
<text x="705" y="236" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">POST Zapier targetUrl</text>
<rect x="850" y="88" width="250" height="250" rx="10" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<rect x="864" y="104" width="222" height="26" rx="4" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="0"/>
<text x="975.0" y="121.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="700" fill="#FFFFFF" text-anchor="middle">ZAPIER_USAGE</text>
<text x="975" y="156" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="600" fill="#134848" text-anchor="middle">verae.zapier.usage</text>
<text x="975" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Optional</text>
<text x="975" y="200" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle"></text>
<text x="975" y="218" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">consumer: usage-writer</text>
<text x="975" y="236" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">billing export</text>
<line x1="290" y1="213" x2="310" y2="213" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="560" y1="213" x2="580" y2="213" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="830" y1="213" x2="850" y2="213" stroke="#94A3B8" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="40" y="364" width="1020" height="168" rx="10" fill="#F3EDE0" fill-opacity="1" stroke="#E0D2B4" stroke-width="1.4"/>
<text x="60" y="392" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="start">Ack semantics</text>
<text x="60" y="418" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">Job still pending Nak with delay ~ intervalMs or republish attempt+1</text>
<text x="60" y="440" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">Job terminal publish jobs.events, then Ack the watch message</text>
<text x="60" y="462" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">Webhook HTTP 2xx Ack · 5xx / network Nak until max_deliver</text>
<text x="60" y="484" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">Poison message terminate after max_deliver; DEBUG_VERAE=webhooks DLQ log</text>
<text x="60" y="506" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">Prefer tokenRef over embedding a Verae JWT in any NATS payload</text>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="640"
viewBox="0 0 1100 640" role="img" aria-label="Zapier operations mapped to middleware and Verae">
<title>Zapier operations mapped to middleware and Verae</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">OPERATIONS MAP</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Connector → /zapier/v1 → Verae wrap</text>
<rect x="20" y="78" width="1060" height="36" rx="0" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="40" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="start">Zapier</text>
<text x="280" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="start">Type</text>
<text x="400" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="start">Middleware</text>
<text x="700" y="101" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="start">Verae</text>
<rect x="20" y="114" width="1060" height="52" rx="0" fill="#FFFFFF" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="146" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Create Timestamp</text>
<text x="280" y="146" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">create</text>
<text x="400" y="146" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">POST /timestamp</text>
<text x="700" y="146" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">POST /api/timestamp → 202 jobId</text>
<rect x="20" y="166" width="1060" height="52" rx="0" fill="#EEF2F6" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="198" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Create Timestamp and Wait</text>
<text x="280" y="198" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">create</text>
<text x="400" y="198" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">POST /timestamp/wait</text>
<text x="700" y="198" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">create + poll / wait</text>
<rect x="20" y="218" width="1060" height="52" rx="0" fill="#FFFFFF" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="250" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Create Batch Timestamps</text>
<text x="280" y="250" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">create</text>
<text x="400" y="250" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">POST /timestamp/batch</text>
<text x="700" y="250" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">POST /api/batch/timestamp</text>
<rect x="20" y="270" width="1060" height="52" rx="0" fill="#EEF2F6" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="302" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Verify Certificate</text>
<text x="280" y="302" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">create</text>
<text x="400" y="302" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">POST /verify</text>
<text x="700" y="302" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">POST /api/verify</text>
<rect x="20" y="322" width="1060" height="52" rx="0" fill="#FFFFFF" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="354" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Find Job Status</text>
<text x="280" y="354" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">search</text>
<text x="400" y="354" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">GET /status/{jobId}</text>
<text x="700" y="354" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">GET /api/status/{jobId}</text>
<rect x="20" y="374" width="1060" height="52" rx="0" fill="#EEF2F6" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="406" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Find Job Verification</text>
<text x="280" y="406" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">search (TS)</text>
<text x="400" y="406" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">GET /status/{jobId}/verification</text>
<text x="700" y="406" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">GET /api/verify/{jobId}</text>
<rect x="20" y="426" width="1060" height="52" rx="0" fill="#FFFFFF" fill-opacity="1" stroke="#94A3B8" stroke-width="0.6"/>
<text x="40" y="458" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="600" fill="#0F2744" text-anchor="start">Timestamp Completed</text>
<text x="280" y="458" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">hook</text>
<text x="400" y="458" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="600" fill="#134848" text-anchor="start">POST /webhooks/subscribe</text>
<text x="700" y="458" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">(no Verae hook — middleware stores targetUrl)</text>
<text x="24" y="500" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#64748B" text-anchor="start">Creates return one object. Searches and hook perform return arrays. 404 on status search → [].</text>
<rect x="20" y="520" width="340" height="96" rx="8" fill="#FFF1E6" fill-opacity="1" stroke="#F0D2B8" stroke-width="1.4"/>
<text x="36" y="546" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#C2410C" text-anchor="start">Zapier cloud</text>
<text x="36" y="566" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">scratch/veraetime</text>
<text x="36" y="580" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">or verae-zapier</text>
<rect x="390" y="520" width="340" height="96" rx="8" fill="#E8F1F8" fill-opacity="1" stroke="#C5D6E8" stroke-width="1.4"/>
<text x="406" y="546" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#1A6B6B" text-anchor="start">Middleware :3100</text>
<text x="406" y="566" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">api_base_url / MIDDLEWARE_BASE_URL</text>
<text x="406" y="580" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">never api.veraetime.net from Zapier</text>
<rect x="760" y="520" width="320" height="96" rx="8" fill="#E8F6EE" fill-opacity="1" stroke="#B7DCC7" stroke-width="1.4"/>
<text x="776" y="546" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#15803D" text-anchor="start">Verae API</text>
<text x="776" y="566" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">JWT server-side only</text>
<text x="776" y="580" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="start">OpenAPI in scratch/our-api/</text>
<line x1="360" y1="568" x2="390" y2="568" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="730" y1="568" x2="760" y2="568" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="580"
viewBox="0 0 1100 580" role="img" aria-label="Middleware internals">
<title>Middleware internals</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">MIDDLEWARE INTERNALS</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">verae-zapier-api/verae-zapier-middleware</text>
<rect x="20" y="80" width="160" height="70" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="100" y="110" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">GET /health</text>
<text x="100" y="128" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="400" fill="#334155" text-anchor="middle">liveness</text>
<rect x="200" y="80" width="880" height="70" rx="8" fill="#0F2744" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="640" y="110" font-family="Helvetica, Arial, sans-serif" font-size="16" font-weight="700" fill="#FFFFFF" text-anchor="middle">Express /zapier → /v1</text>
<text x="640" y="132" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#CBD5E1" text-anchor="middle">authenticate · rateLimit on protected routes</text>
<rect x="20" y="172" width="340" height="180" rx="8" fill="#E8F1F8" fill-opacity="1" stroke="#C5D6E8" stroke-width="1.4"/>
<text x="36" y="198" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#1A6B6B" text-anchor="start">Public</text>
<text x="36" y="222" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">POST /v1/auth/login</text>
<text x="36" y="248" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">GET /v1/auth/me</text>
<text x="36" y="274" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">POST /v1/signup</text>
<text x="36" y="300" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">GET /v1/admin/* (X-Admin-Secret)</text>
<rect x="380" y="172" width="360" height="180" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="396" y="198" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="start">Protected /v1</text>
<text x="396" y="222" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">/timestamp /timestamp/wait /batch</text>
<text x="396" y="248" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">/verify</text>
<text x="396" y="274" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">/status/:jobId[/verification]</text>
<text x="396" y="300" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">/webhooks/subscribe|unsubscribe</text>
<rect x="760" y="172" width="320" height="180" rx="8" fill="#F3EDE0" fill-opacity="1" stroke="#E0D2B4" stroke-width="1.4"/>
<text x="776" y="198" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#B45309" text-anchor="start">Services + store</text>
<text x="776" y="222" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">auth / entitlement / tenant</text>
<text x="776" y="248" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">timestamp / verify / webhook</text>
<text x="776" y="274" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">store.json tenants usage hooks</text>
<text x="776" y="300" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">veraeClient (+ mock)</text>
<rect x="20" y="372" width="520" height="180" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="36" y="400" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="start">Flags</text>
<text x="36" y="426" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">NATS_ENABLED=false in-process job poller (Phase 6 product path)</text>
<text x="36" y="452" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">NATS_ENABLED=true JetStream workers; no in-process poller</text>
<text x="36" y="478" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">MOCK_VERAE=true deterministic jobs, no live Verae</text>
<text x="36" y="504" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">DEBUG_VERAE=auth,nats,jobs,webhooks,http,billing</text>
<rect x="560" y="372" width="520" height="180" rx="8" fill="#E8F6EE" fill-opacity="1" stroke="#B7DCC7" stroke-width="1.4"/>
<text x="576" y="400" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#15803D" text-anchor="start">Outbound</text>
<text x="576" y="426" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">HTTPS VERAE_API_BASE_URL (prod: api.veraetime.net)</text>
<text x="576" y="452" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">NATS NATS_URL nats://127.0.0.1:4222</text>
<text x="576" y="478" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">HTTPS Zapier targetUrl (webhook worker)</text>
<text x="576" y="504" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">Never bind NATS to a public interface</text>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="520"
viewBox="0 0 1100 520" role="img" aria-label="Implementation phase roadmap">
<title>Implementation phase roadmap</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">ROADMAP</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">TODO.md phases and gates · do not skip</text>
<rect x="40" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="105" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">0 Docs</text>
<text x="105" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="170" y1="128" x2="190" y2="128" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="190" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="255" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">1 Debug</text>
<text x="255" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="320" y1="128" x2="340" y2="128" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="340" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="405" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">2 HTTP</text>
<text x="405" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="470" y1="128" x2="490" y2="128" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="490" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="555" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">3 Store</text>
<text x="555" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="620" y1="128" x2="640" y2="128" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="640" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="705" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">4 Client</text>
<text x="705" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="770" y1="128" x2="790" y2="128" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="790" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="855" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">5 Auth</text>
<text x="855" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="920" y1="128" x2="940" y2="128" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="940" y="100" width="130" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="1005" y="124" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">6 Sync API</text>
<text x="1005" y="142" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="1005" y1="156" x2="1005" y2="190" stroke="#1A6B6B" stroke-width="1.8"/>
<line x1="24" y1="190" x2="1005" y2="190" stroke="#1A6B6B" stroke-width="1.8"/>
<line x1="24" y1="190" x2="24" y2="428" stroke="#1A6B6B" stroke-width="1.8"/>
<line x1="24" y1="238" x2="40" y2="238" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="40" y="210" width="180" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="130" y="234" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">7 NATS</text>
<text x="130" y="252" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="220" y1="238" x2="240" y2="238" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="240" y="210" width="180" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="330" y="234" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">8 Workers</text>
<text x="330" y="252" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="420" y1="238" x2="440" y2="238" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="440" y="210" width="180" height="56" rx="8" fill="#FEF3C7" fill-opacity="1" stroke="#B45309" stroke-width="1.4"/>
<text x="530" y="234" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">9 Wait-via-NATS</text>
<text x="530" y="252" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#64748B" text-anchor="middle">open blocker</text>
<line x1="24" y1="338" x2="40" y2="338" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="40" y="310" width="180" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="130" y="334" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">10 Tenancy</text>
<text x="130" y="352" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="24" y1="428" x2="40" y2="428" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="40" y="400" width="180" height="56" rx="8" fill="#1A6B6B" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="130" y="424" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#FFFFFF" text-anchor="middle">11 Zapier app</text>
<text x="130" y="442" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#99F6E4" text-anchor="middle">gate passed</text>
<line x1="220" y1="428" x2="240" y2="428" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="240" y="400" width="180" height="56" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="330" y="424" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">12 E2E local</text>
<text x="330" y="442" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#64748B" text-anchor="middle">next</text>
<line x1="420" y1="428" x2="440" y2="428" stroke="#94A3B8" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="440" y="400" width="180" height="56" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="530" y="424" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">13 Production</text>
<text x="530" y="442" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#64748B" text-anchor="middle">next</text>
<line x1="620" y1="428" x2="640" y2="428" stroke="#94A3B8" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="640" y="400" width="180" height="56" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="730" y="424" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">14 Private push</text>
<text x="730" y="442" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#64748B" text-anchor="middle">next</text>
<line x1="820" y1="428" x2="840" y2="428" stroke="#94A3B8" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="840" y="400" width="180" height="56" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="930" y="424" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#0F2744" text-anchor="middle">15 Harden</text>
<text x="930" y="442" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="500" fill="#64748B" text-anchor="middle">next</text>
<text x="24" y="500" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#64748B" text-anchor="start">Phase 9 is the open blocker before multi-instance wait. Phase 12 needs 8 + 10 + 11.</text>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="560"
viewBox="0 0 1100 560" role="img" aria-label="Research workspace and integration path">
<title>Research workspace and integration path</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">THIS WORKSPACE</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">How Grok, CLIs, Mongo, and the stack fit together</text>
<rect x="20" y="80" width="340" height="220" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="36" y="108" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="start">Laptop · this repo</text>
<text x="36" y="132" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">getting-started.md / .pdf</text>
<text x="36" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">scratch/veraetime</text>
<text x="36" y="180" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">verae-zapier-api/</text>
<text x="36" y="204" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">docs/diagrams/</text>
<text x="36" y="228" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">/zapier-build skill</text>
<text x="36" y="252" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">scripts/restart-grok.sh</text>
<rect x="380" y="80" width="340" height="220" rx="8" fill="#E8F1F8" fill-opacity="1" stroke="#C5D6E8" stroke-width="1.4"/>
<text x="396" y="108" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#1A6B6B" text-anchor="start">Local runtime</text>
<text x="396" y="132" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">zapier-platform 19.1.0</text>
<text x="396" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">zapier-sdk 0.77.1</text>
<text x="396" y="180" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">middleware :3100</text>
<text x="396" y="204" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">optional NATS :4222</text>
<text x="396" y="228" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">build + validate (no login)</text>
<text x="396" y="252" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">register / push need ~/.zapierrc</text>
<rect x="740" y="80" width="340" height="220" rx="8" fill="#F3EDE0" fill-opacity="1" stroke="#E0D2B4" stroke-width="1.4"/>
<text x="756" y="108" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#B45309" text-anchor="start">NS1 Mongo (tunneled)</text>
<text x="756" y="132" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">ssh -L 27017:127.0.0.1:27017 ns1</text>
<text x="756" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">db zapier</text>
<text x="756" y="180" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">apps · templates · help</text>
<text x="756" y="204" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">platform_reference</text>
<text x="756" y="228" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">never 70.88.205.138:27017 public</text>
<text x="756" y="252" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">creds in ~/.mcp-env only</text>
<line x1="360" y1="190" x2="380" y2="190" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="720" y1="190" x2="740" y2="190" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<text x="730.0" y="179.0" font-family="Helvetica, Arial, sans-serif" font-size="10" font-weight="600" fill="#134848" text-anchor="middle">tunnel</text>
<rect x="20" y="324" width="520" height="208" rx="8" fill="#FFF1E6" fill-opacity="1" stroke="#F0D2B8" stroke-width="1.4"/>
<text x="36" y="352" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#C2410C" text-anchor="start">Publish path</text>
<text x="36" y="378" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">1 validate locally against middleware</text>
<text x="36" y="404" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">2 zapier-platform login (browser)</text>
<text x="36" y="430" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">3 public HTTPS middleware (Phase 13)</text>
<text x="36" y="456" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">4 register + push private version (Phase 14)</text>
<text x="36" y="482" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">5 one human E2E Zap, then consider listing</text>
<rect x="560" y="324" width="520" height="208" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="576" y="352" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="start">Consume path (optional, not Verae publish)</text>
<text x="576" y="378" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">zapier-sdk login → call existing apps</text>
<text x="576" y="404" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">Hosted MCP mcp.zapier.com/api/v1/connect</text>
<text x="576" y="430" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">discover → enable → inspect → execute</text>
<text x="576" y="456" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">writes need explicit approval; 2 tasks each</text>
<text x="576" y="482" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">do not mix with zapier-platform</text>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="620"
viewBox="0 0 1100 620" role="img" aria-label="Zapier and Verae billing layers">
<title>Zapier and Verae billing layers</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">BILLING</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="20" font-weight="700" fill="#0F2744" text-anchor="start">Two meters · Zapier tasks + Verae timestamps</text>
<text x="24" y="72" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#64748B" text-anchor="start">Publishing the Verae app is free. The customers Zapier plan is not Veraes invoice.</text>
<rect x="20" y="88" width="1060" height="80" rx="10" fill="#FFF1E6" fill-opacity="1" stroke="#C2410C" stroke-width="1.4"/>
<text x="40" y="116" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="start">A Zapier customer plan</text>
<text x="40" y="138" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Tasks / month (or Enterprise annual pool)</text>
<text x="40" y="156" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Free 100 · Pro from 750 · Team from 2k · Ent custom</text>
<rect x="20" y="178" width="1060" height="80" rx="10" fill="#E8F1F8" fill-opacity="1" stroke="#1A6B6B" stroke-width="1.4"/>
<text x="40" y="206" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="start">B Task multipliers</text>
<text x="40" y="228" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">1 = typical action (Create Timestamp)</text>
<text x="40" y="246" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">MCP execute = 2 AI Advanced = 3 Premium = 5</text>
<rect x="20" y="268" width="1060" height="80" rx="10" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="40" y="296" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="start">C Overflow</text>
<text x="40" y="318" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Pay-per-task on paid plans</text>
<text x="40" y="336" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Annual 1.25x · monthly 2.5x · cap 3x then pause</text>
<rect x="20" y="358" width="1060" height="80" rx="10" fill="#E8F6EE" fill-opacity="1" stroke="#15803D" stroke-width="1.4"/>
<text x="40" y="386" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="start">D Verae middleware plan</text>
<text x="40" y="408" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Timestamps / verifications / batch / RPM</text>
<text x="40" y="426" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">free 50 · starter 500 · pro 5k · enterprise contract</text>
<rect x="20" y="448" width="1060" height="80" rx="10" fill="#F3EDE0" fill-opacity="1" stroke="#B45309" stroke-width="1.4"/>
<text x="40" y="476" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="start">E Partner / embed</text>
<text x="40" y="498" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Directory publish = $0 to Verae</text>
<text x="40" y="516" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">White Label: Zapier bills the product co. (usage)</text>
<rect x="20" y="538" width="1060" height="80" rx="10" fill="#F1F5F9" fill-opacity="1" stroke="#334155" stroke-width="1.4"/>
<text x="40" y="566" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="700" fill="#0F2744" text-anchor="start">F Add-ons (not tasks)</text>
<text x="40" y="588" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Agents = activities Chatbots = seat/tier</text>
<text x="40" y="606" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#334155" text-anchor="start">Do not mix into the Zapier task estimate</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="680"
viewBox="0 0 1100 680" role="img" aria-label="Timestamped files on Peergos with tiered IPFS and cold retrieve">
<title>Timestamped files on Peergos with tiered IPFS and cold retrieve</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1A6B6B"/>
</marker>
<marker id="arrow-navy" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#0F2744"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#C2410C"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#F8FAFC"/>
<text x="24" y="28" font-family="Helvetica, Arial, sans-serif" font-size="11" font-weight="700" fill="#1A6B6B" text-anchor="start">CLIENT PATTERN</text>
<text x="24" y="50" font-family="Helvetica, Arial, sans-serif" font-size="18" font-weight="700" fill="#0F2744" text-anchor="start">Timestamp · metadata · Peergos · pin cache · cold retrieve</text>
<text x="24" y="70" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="500" fill="#64748B" text-anchor="start">Keep the index hot. Do not keep every CID live on a gateway.</text>
<rect x="20" y="88" width="1060" height="200" rx="12" fill="#E8F1F8" fill-opacity="1" stroke="#C5D6E8" stroke-width="1.4"/>
<text x="36" y="112" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="700" fill="#1A6B6B" text-anchor="start">WRITE PATH (Zapier orchestrates; Verae timestamps the hash)</text>
<rect x="40" y="128" width="190" height="132" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="135" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">1 Source</text>
<text x="135" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Drive / mail /</text>
<text x="135" y="202" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Peergos outbox</text>
<rect x="250" y="128" width="190" height="132" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="345" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">2 Hash</text>
<text x="345" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">SHA-256 or</text>
<text x="345" y="202" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">envelope JSON</text>
<rect x="460" y="128" width="190" height="132" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="555" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">3 Verae</text>
<text x="555" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Create Timestamp</text>
<text x="555" y="202" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">jobId + hook</text>
<rect x="670" y="128" width="190" height="132" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="765" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">4 Store</text>
<text x="765" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">Peergos e2e file</text>
<text x="765" y="202" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">optional hot pin</text>
<rect x="880" y="128" width="190" height="132" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.4"/>
<text x="975" y="156" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="middle">5 Index</text>
<text x="975" y="182" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">cid · jobId · class</text>
<text x="975" y="202" font-family="Helvetica, Arial, sans-serif" font-size="12" font-weight="400" fill="#334155" text-anchor="middle">always on</text>
<line x1="230" y1="194" x2="250" y2="194" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="440" y1="194" x2="460" y2="194" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="650" y1="194" x2="670" y2="194" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<line x1="860" y1="194" x2="880" y2="194" stroke="#1A6B6B" stroke-width="1.8" marker-end="url(#arrow)"/>
<rect x="20" y="308" width="340" height="200" rx="8" fill="#FFF1E6" fill-opacity="1" stroke="#F0D2B8" stroke-width="1.4"/>
<text x="36" y="336" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#C2410C" text-anchor="start">HOT · cache pin</text>
<text x="36" y="362" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">Kubo / Pinata / Filebase</text>
<text x="36" y="386" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">Pinning Services API</text>
<text x="36" y="410" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">TTL = pinnedUntil</text>
<text x="36" y="434" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">drop when cold copy exists</text>
<rect x="380" y="308" width="340" height="200" rx="8" fill="#E8F6EE" fill-opacity="1" stroke="#B7DCC7" stroke-width="1.4"/>
<text x="396" y="336" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#15803D" text-anchor="start">WARM · Peergos</text>
<text x="396" y="362" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">e2e encrypted IPFS blocks</text>
<text x="396" y="386" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">user capabilities / sharing</text>
<text x="396" y="410" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">host cannot read plaintext</text>
<text x="396" y="434" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">not a public CDN</text>
<rect x="740" y="308" width="340" height="200" rx="8" fill="#F3EDE0" fill-opacity="1" stroke="#E0D2B4" stroke-width="1.4"/>
<text x="756" y="336" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#B45309" text-anchor="start">COLD · retrieve on demand</text>
<text x="756" y="362" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">S3 Glacier IR / Flexible / Deep</text>
<text x="756" y="386" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">or Filecoin / B2 / Storj</text>
<text x="756" y="410" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">Iceberg = catalog, not bytes</text>
<text x="756" y="434" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">restore API → short re-pin</text>
<rect x="20" y="528" width="1060" height="128" rx="8" fill="#FFFFFF" fill-opacity="1" stroke="#0F2744" stroke-width="1.8"/>
<text x="36" y="556" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="700" fill="#0F2744" text-anchor="start">READ PATH GET /ipfs/{cid} or gateway miss</text>
<text x="36" y="582" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">1 look up index by cid (always-on). 2 pin-hot hit → serve. 3 Peergos capability → fetch encrypted blocks.</text>
<text x="36" y="604" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">4 storageClass glacier-* → StartRestore / vendor retrieve → 202 + Retry-After → rehydrate → optional short pin → serve.</text>
<text x="36" y="626" font-family="Helvetica, Arial, sans-serif" font-size="13" font-weight="400" fill="#334155" text-anchor="start">Never walk every pin provider. Never put raw files or Verae JWTs in the Zap.</text>
</svg>

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -111,7 +111,7 @@ Streams: `ZAPIER_JOBS`, `ZAPIER_EVENTS`, `ZAPIER_WEBHOOKS` (optional `ZAPIER_USA
- Swagger UI: https://api.veraetime.net/docs/swagger/index.html - Swagger UI: https://api.veraetime.net/docs/swagger/index.html
- OpenAPI: https://api.veraetime.net/docs/swagger/openapi.yaml - OpenAPI: https://api.veraetime.net/docs/swagger/openapi.yaml
- Local copy: [scratch/our-api/openapi.yaml](scratch/our-api/openapi.yaml) - Local copy: [veraetime-openapi.yaml](veraetime-openapi.yaml)
- Auth: `POST /auth/login` → JWT in `token`; all other API routes `Authorization: Bearer <token>` - Auth: `POST /auth/login` → JWT in `token`; all other API routes `Authorization: Bearer <token>`
- Async create: `POST /api/timestamp`**202** `{ jobId }` - Async create: `POST /api/timestamp`**202** `{ jobId }`

View file

@ -10,30 +10,32 @@ Implementation status follows [TODO.md](../../../TODO.md) phases. Docs describe
|------------|-------------|-------| |------------|-------------|-------|
| [debug.md](debug.md) | `debug/` | 1 | | [debug.md](debug.md) | `debug/` | 1 |
| [config.md](config.md) | `config.js` | 2 | | [config.md](config.md) | `config.js` | 2 |
| [errors.md](errors.md) | `errors.js` | 2 | | [errors.md](../../modules/verae-zapier-middleware/errors.md) | `errors.js` | 2 |
| [app.md](app.md) | `app.js`, `index.js` | 2 | | [app.md](../../modules/verae-zapier-middleware/app.md) | `app.js`, `index.js` | 2 |
| [store-db.md](store-db.md) | `store/db.js` | 3 | | [store/db.md](../../modules/verae-zapier-middleware/store/db.md) | `store/db.js` | 3 |
| [store-tenants.md](store-tenants.md) | `store/tenants.js` | 3 | | [store/tenants.md](../../modules/verae-zapier-middleware/store/tenants.md) | `store/tenants.js` | 3 |
| [store-usage.md](store-usage.md) | `store/usage.js` | 3 | | [store/usage.md](../../modules/verae-zapier-middleware/store/usage.md) | `store/usage.js` | 3 |
| [store-webhooks.md](store-webhooks.md) | `store/webhooks.js` | 3 | | [store/webhooks.md](../../modules/verae-zapier-middleware/store/webhooks.md) | `store/webhooks.js` | 3 |
| [tokens.md](tokens.md) | `lib/tokens.js` | 4 | | [lib/tokens.md](../../modules/verae-zapier-middleware/lib/tokens.md) | `lib/tokens.js` | 4 |
| [veraeClient.md](veraeClient.md) | `clients/veraeClient.js` | 4 | | [veraeClient.md](../../modules/verae-zapier-middleware/clients/veraeClient.md) | `clients/veraeClient.js` | 4 |
| [authService.md](authService.md) | `services/authService.js` | 5 | | [authService.md](../../modules/verae-zapier-middleware/services/authService.md) | `services/authService.js` | 5 |
| [entitlementService.md](entitlementService.md) | `services/entitlementService.js` | 5 | | [entitlementService.md](../../modules/verae-zapier-middleware/services/entitlementService.md) | `services/entitlementService.js` | 5 |
| [middleware-http.md](middleware-http.md) | `middleware/*.js` | 5 | | [authenticate.md](../../modules/verae-zapier-middleware/middleware/authenticate.md) | `middleware/*.js` | 5 |
| [timestampService.md](timestampService.md) | `services/timestampService.js` | 6 | | [timestampService.md](../../modules/verae-zapier-middleware/services/timestampService.md) | `services/timestampService.js` | 6 |
| [verifyService.md](verifyService.md) | `services/verifyService.js` | 6 | | [verifyService.md](../../modules/verae-zapier-middleware/services/verifyService.md) | `services/verifyService.js` | 6 |
| [webhookService.md](webhookService.md) | `services/webhookService.js` | 6 | | [webhookService.md](../../modules/verae-zapier-middleware/services/webhookService.md) | `services/webhookService.js` | 6 |
| [routes.md](routes.md) | `routes/*.js` | 6 | | [routes.md](../../modules/verae-zapier-middleware/routes/index.md) | `routes/*.js` | 6 |
| [nats.md](nats.md) | `nats/*.js` | 7 | | [nats.md](nats.md) | `nats/*.js` | 7 |
| [workers.md](workers.md) | `workers/*.js` | 89 | | [workers.md](workers.md) | `workers/*.js` | 89 |
| [tenantService.md](tenantService.md) | `services/tenantService.js` | 10 | | [tenantService.md](../../modules/verae-zapier-middleware/services/tenantService.md) | `services/tenantService.js` | 10 |
Full export tables also live in [function-reference.md](function-reference.md).
## Zapier app (`verae-zapier`) ## Zapier app (`verae-zapier`)
| Module doc | Source path | Phase | | Module doc | Source path | Phase |
|------------|-------------|-------| |------------|-------------|-------|
| [zapier-app.md](zapier-app.md) | `index.js`, `authentication.js`, creates/searches/triggers | 11 | | [verae-zapier/index.md](../../modules/verae-zapier/index.md) | `index.js`, `authentication.js`, creates/searches/triggers | 11 |
## Documentation rules ## Documentation rules

View file

@ -15,7 +15,7 @@
## Function contracts ## Function contracts
Full I/O tables: [function-reference.md](function-reference.md#natssubjectsjs) Full I/O tables: [function-reference.md](function-reference.md#natssubjectsjs)
Payload schemas: [../../architecture/nats-subjects.md](../../architecture/nats-subjects.md) Payload schemas: [../../02-architecture/nats-subjects.md](../../02-architecture/nats-subjects.md)
## Implementation status ## Implementation status

View file

@ -0,0 +1,19 @@
# Docs catalog. DNS: zapier.georgelambert.org → 70.88.205.138
# rsync target: /SSD2/sites/zapier.georgelambert.org
http://zapier.georgelambert.org {
redir https://zapier.georgelambert.org{uri} permanent
}
zapier.georgelambert.org {
root * /SSD2/sites/zapier.georgelambert.org
encode gzip zstd
file_server {
index index.html index.pdf README.html README.pdf
}
try_files {path} {path}/index.html {path}/index.pdf {path}/README.html {path}/README.pdf
header {
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
}
}

View file

@ -12,6 +12,7 @@ import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from urllib.parse import unquote
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
SITE = ROOT / "site" SITE = ROOT / "site"
@ -219,6 +220,180 @@ def render_md(src: Path, html_dest: Path, pdf_dest: Path, title: str) -> str:
return "" return ""
def _file_index(site: Path) -> dict[str, list[str]]:
idx: dict[str, list[str]] = {}
for p in site.rglob("*"):
if p.is_file():
rel = p.relative_to(site).as_posix()
idx.setdefault(p.name, []).append(rel)
return idx
_PREFERRED_PREFIXES = (
"overview/",
"packages/overview/",
"packages/verae-ops/",
"packages/verae-fleet/docs/",
"user-docs/",
"packages/zapier-user-docs/",
"docs/02-architecture/",
"docs/modules/",
"docs-master/",
"packages/zapier-decisions/",
"docs/modules-pdf/",
"docs/models-pdf/",
"sphinx/",
)
def _pick_hit(hits: list[str]) -> str:
for prefix in _PREFERRED_PREFIXES:
for h in hits:
if h.startswith(prefix):
return h
return hits[0]
def _web_target(site: Path, rel: str, index: dict[str, list[str]] | None = None) -> str:
"""Map a site-relative path to a file that actually exists."""
rel = unquote(rel).split("#", 1)[0].lstrip("/")
if rel.endswith(".html.pdf"):
rel = rel[:-4]
aliases = {
"index-md.pdf": "index-md.html",
"index-md.md": "index-md.html",
"docs/architecture/overview.pdf": "docs/02-architecture/overview.pdf",
"docs/architecture/nats-subjects.pdf": "docs/02-architecture/nats-subjects.pdf",
"docs/architecture/nats-gateway.pdf": "docs/02-architecture/nats-gateway.pdf",
"docs/architecture/composition.pdf": "docs/02-architecture/composition.pdf",
"docs/architecture/fleet.pdf": "docs/02-architecture/fleet.pdf",
"docs/sphinx/_build/html/index.pdf": "sphinx/index.html",
"docs/sphinx/_build/html/index.html": "sphinx/index.html",
}
rel = aliases.get(rel, rel)
candidates = [rel]
if rel.endswith(".pdf"):
candidates.append(rel[:-4] + ".html")
candidates.append(rel[:-4] + ".md")
if rel.endswith(".md"):
candidates.append(rel[:-3] + ".pdf")
candidates.append(rel[:-3] + ".html")
stem = Path(rel).name
if not stem or stem in {".", "/"}:
return rel or "index.html"
stem_pdf = Path(stem).with_suffix(".pdf").as_posix()
stem_html = Path(stem).with_suffix(".html").as_posix()
for folder in (
"overview",
"packages/overview",
"packages/verae-ops",
"packages/verae-fleet/docs",
"user-docs",
"packages/zapier-user-docs",
"docs/02-architecture",
"docs/modules-pdf",
"docs/models-pdf",
"packages/zapier-decisions",
"docs-master",
"sphinx",
):
candidates.append(f"{folder}/{stem_pdf}")
candidates.append(f"{folder}/{stem_html}")
for c in candidates:
if (site / c).is_file():
return c
if (site / c).is_dir():
for name in ("index.html", "index.pdf", "README.pdf", "README.html"):
if (site / c / name).is_file():
return f"{c.rstrip('/')}/{name}"
if index:
names = [stem, stem_pdf, stem_html]
if stem.endswith(".md"):
names.append(Path(stem).with_suffix(".pdf").as_posix())
names.append(Path(stem).with_suffix(".html").as_posix())
for name in names:
hits = index.get(name) or []
if hits:
return _pick_hit(hits)
return rel
def rewrite_pdf_uris(site: Path) -> None:
"""Turn WeasyPrint file:///… annotations into https://zapier.georgelambert.org/…"""
try:
from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject, create_string_object
except ImportError:
print("pypdf missing; PDF URI rewrite skipped")
return
web = "https://zapier.georgelambert.org/"
prefix = site.resolve().as_uri().rstrip("/") + "/"
index = _file_index(site)
def resolve(raw: str) -> str:
frag = ""
if "#" in raw:
raw, frag = raw.split("#", 1)
frag = "#" + frag
mapped = raw
if raw.startswith(prefix):
mapped = web + _web_target(site, raw[len(prefix) :], index)
elif raw.startswith("file:"):
name = unquote(raw.rsplit("/", 1)[-1] if "/" in raw else raw)
if not name or name in {".", "/", "file:"}:
mapped = web
else:
hit = _web_target(site, name, index)
if (site / hit).exists():
mapped = web + hit
elif "index-md" in raw:
mapped = web + "index-md.html"
else:
mapped = web
elif raw.startswith(web):
hit = _web_target(site, raw[len(web) :], index)
if (site / hit).is_file() or (site / hit).is_dir():
mapped = web + hit
if mapped != raw:
return mapped + frag
return raw + frag
n_pdf = 0
n_fix = 0
for pdf in site.rglob("*.pdf"):
try:
reader = PdfReader(str(pdf))
except Exception:
continue
changed = False
for page in reader.pages:
annots = page.get("/Annots")
if not annots:
continue
for annot in annots:
obj = annot.get_object()
action = obj.get("/A")
if not action:
continue
uri = action.get("/URI")
if not uri:
continue
raw = str(uri)
new = resolve(raw)
if new != raw:
action[NameObject("/URI")] = create_string_object(new)
changed = True
n_fix += 1
if changed:
writer = PdfWriter(clone_from=reader)
tmp = pdf.with_suffix(".pdf.tmp")
with tmp.open("wb") as fh:
writer.write(fh)
tmp.replace(pdf)
n_pdf += 1
print(f"rewrote {n_fix} PDF URIs in {n_pdf} files → {web}")
def convert_all_markdown(copied: list[tuple[Path, Path, str]]) -> list[str]: def convert_all_markdown(copied: list[tuple[Path, Path, str]]) -> list[str]:
errors = [] errors = []
n = len(copied) n = len(copied)
@ -403,14 +578,20 @@ def main() -> None:
SITE / "packages" / pkg / "docs", SITE / "packages" / pkg / "docs",
ignore=shutil.ignore_patterns("node_modules"), ignore=shutil.ignore_patterns("node_modules"),
) )
copy_tree(pkg_root / "services", SITE / "packages" / pkg / "services")
elif pkg == "verae-nats-process": elif pkg == "verae-nats-process":
for p in pkg_root.glob("*.md"): for p in pkg_root.glob("*.md"):
copy_tree(p, SITE / "packages" / pkg / p.name) copy_tree(p, SITE / "packages" / pkg / p.name)
else: else:
for name in ("README.md", "SUMMARY.md", "NATS.md"): extras = ("README.md", "SUMMARY.md", "NATS.md")
if pkg == "zapier-decisions":
extras = extras + ("LOG.md", "TODO.md")
for name in extras:
p = pkg_root / name p = pkg_root / name
if p.exists(): if p.exists():
copy_tree(p, SITE / "packages" / pkg / name) copy_tree(p, SITE / "packages" / pkg / name)
if pkg == "zapier-decisions":
copy_tree(pkg_root / "decisions", SITE / "packages" / pkg / "decisions")
ui_docs = ROOT / "packages" / "ui-docs" ui_docs = ROOT / "packages" / "ui-docs"
for name in ("WALKTHROUGH.md", "REPORT.md", "UI-REVIEW.pdf"): for name in ("WALKTHROUGH.md", "REPORT.md", "UI-REVIEW.pdf"):
p = ui_docs / name p = ui_docs / name
@ -432,6 +613,7 @@ def main() -> None:
SITE / "packages" / "zappier" / "docs", SITE / "packages" / "zappier" / "docs",
ignore=shutil.ignore_patterns("screenshots", "walkthrough", "superpowers"), ignore=shutil.ignore_patterns("screenshots", "walkthrough", "superpowers"),
) )
copy_tree(ROOT / "packages" / "zappier" / "openapi.yaml", SITE / "packages" / "zappier" / "openapi.yaml")
for name in ("README.md", "TODO.md", "OPEN.md"): for name in ("README.md", "TODO.md", "OPEN.md"):
src = ROOT / name src = ROOT / name
if not src.exists() and name == "OPEN.md": if not src.exists() and name == "OPEN.md":
@ -469,12 +651,40 @@ def main() -> None:
if diagrams.exists(): if diagrams.exists():
copy_tree(diagrams, SITE / "research" / "docs" / "diagrams") copy_tree(diagrams, SITE / "research" / "docs" / "diagrams")
def write_book_index(folder: Path, title: str, patterns: tuple[str, ...]) -> None:
if not folder.is_dir():
return
files: list[Path] = []
for pat in patterns:
files.extend(p for p in folder.rglob(pat) if p.is_file())
skip = {"index.md", "index.html", "index.pdf"}
files = sorted({p for p in files if p.name not in skip})
lines = [f"# {title}", "", f"{len(files)} files in this book.", ""]
for p in files:
rel = p.relative_to(folder).as_posix()
lines.append(f"- [{rel}]({rel})")
(folder / "index.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
write_book_index(SITE / "docs" / "modules-pdf", "Module PDFs (book)", ("*.pdf",))
write_book_index(SITE / "docs" / "models-pdf", "Model PDFs (book)", ("*.pdf",))
write_book_index(SITE / "packages" / "ui-docs" / "screenshots", "UI screenshots", ("*.png", "*.jpg", "*.webp"))
write_book_index(SITE / "packages" / "overview" / "diagrams", "Overview diagrams", ("*.svg", "*.png"))
write_book_index(SITE / "overview" / "diagrams", "Overview diagrams", ("*.svg", "*.png"))
write_book_index(SITE / "packages" / "zapier-decisions" / "decisions", "Decisions", ("*.md", "*.pdf"))
write_book_index(SITE / "packages" / "verae-fleet" / "services", "Fleet service JSON", ("*.json",))
write_book_index(SITE / "docs" / "00-sources" / "docs" / "diagrams", "Research diagrams", ("*.svg", "*.png"))
# Render every published .md from the site copy so relative images resolve. # Render every published .md from the site copy so relative images resolve.
to_render: list[tuple[Path, Path, str]] = [] to_render: list[tuple[Path, Path, str]] = []
seen: set[Path] = set() seen: set[Path] = set()
for md in SITE.rglob("*.md"): for md in SITE.rglob("*.md"):
if not md.is_file(): if not md.is_file():
continue continue
if "research" in md.parts:
continue
# Sphinx HTML/PDF are copied from _build; don't WeasyPrint the rst sources.
if "sphinx" in md.parts:
continue
key = md.resolve() key = md.resolve()
if key in seen: if key in seen:
continue continue
@ -496,6 +706,7 @@ def main() -> None:
labeled.append((src, dest, title_map.get(key, title_map.get(src.relative_to(ROOT).as_posix() if src.is_relative_to(ROOT) else key, title)))) labeled.append((src, dest, title_map.get(key, title_map.get(src.relative_to(ROOT).as_posix() if src.is_relative_to(ROOT) else key, title))))
errors = convert_all_markdown(labeled) errors = convert_all_markdown(labeled)
rewrite_pdf_uris(SITE)
# Also write CONSOLE.pdf next to the markdown in the fleet package (local console) # Also write CONSOLE.pdf next to the markdown in the fleet package (local console)
console_md = ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.md" console_md = ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.md"
@ -591,8 +802,8 @@ def main() -> None:
("packages/ui-docs/UI-REVIEW.pdf", "UI review (screenshots + live doors)"), ("packages/ui-docs/UI-REVIEW.pdf", "UI review (screenshots + live doors)"),
("overview/README.pdf", "System overview"), ("overview/README.pdf", "System overview"),
("overview/INDEX.pdf", "Documentation index"), ("overview/INDEX.pdf", "Documentation index"),
("docs/modules-pdf/", "Module PDFs (book)"), ("docs/modules-pdf/index.pdf", "Module PDFs (book)"),
("docs/models-pdf/", "Model PDFs (book)"), ("docs/models-pdf/index.pdf", "Model PDFs (book)"),
("sphinx/index.html", "Sphinx HTML"), ("sphinx/index.html", "Sphinx HTML"),
("sphinx/verae-zapier-modules.pdf", "Sphinx LaTeX PDF"), ("sphinx/verae-zapier-modules.pdf", "Sphinx LaTeX PDF"),
] ]
@ -605,8 +816,10 @@ def main() -> None:
("packages/verae-fleet/docs/CONSOLE.html", "Operator console"), ("packages/verae-fleet/docs/CONSOLE.html", "Operator console"),
("overview/README.html", "System overview"), ("overview/README.html", "System overview"),
("overview/INDEX.html", "Documentation index"), ("overview/INDEX.html", "Documentation index"),
("docs/modules/", "All module markdown"), ("docs/modules/README.html", "All module markdown"),
("docs/models/", "All model markdown"), ("docs/models/README.html", "All model markdown"),
("docs/modules-pdf/index.html", "Module PDFs (book)"),
("docs/models-pdf/index.html", "Model PDFs (book)"),
("sphinx/index.html", "Sphinx HTML"), ("sphinx/index.html", "Sphinx HTML"),
("sphinx/verae-zapier-modules.pdf", "Sphinx LaTeX PDF"), ("sphinx/verae-zapier-modules.pdf", "Sphinx LaTeX PDF"),
] ]
@ -676,12 +889,12 @@ def main() -> None:
) )
def extra_section(items: list[tuple[str, str]]) -> str: def extra_section(items: list[tuple[str, str]]) -> str:
lis = "".join( lis = []
f'<li><a href="{h}">{lab}</a></li>' for h, lab in items:
for h, lab in items dest = SITE / h
if (SITE / h).exists() or h.endswith("/") or h.startswith("sphinx") if dest.exists() or (h.startswith("sphinx") and (SITE / "sphinx").exists()):
) lis.append(f'<li><a href="{h}">{lab}</a></li>')
return f"<section><h2>Catalogs</h2><ul>{lis}</ul></section>" return f"<section><h2>Catalogs</h2><ul>{''.join(lis)}</ul></section>"
rest_pdf = f""" rest_pdf = f"""
{cards("pdf")} {cards("pdf")}

111
scripts/check-doc-links.py Normal file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Check catalog homepage + PDF URI targets after a site build."""
from __future__ import annotations
import sys
from html.parser import HTMLParser
from pathlib import Path
from urllib.parse import unquote, urljoin, urlparse
ROOT = Path(__file__).resolve().parents[1]
SITE = ROOT / "site"
WEB = "https://zapier.georgelambert.org/"
class Anchors(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.hrefs: list[str] = []
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
if tag == "a":
d = dict(attrs)
if d.get("href"):
self.hrefs.append(d["href"] or "")
def local_ok(url: str) -> bool:
if url.startswith(WEB):
rel = url[len(WEB) :].split("#", 1)[0]
dest = SITE / rel
if dest.is_file():
return True
if dest.is_dir() and any(
(dest / n).exists() for n in ("index.html", "index.pdf", "README.html", "README.pdf")
):
return True
# yaml/svg/png on homepage
return False
if url.startswith("file:"):
return False
return True # http(s) off-site; homepage checker uses GET separately
def main() -> int:
idx = SITE / "index.html"
if not idx.exists():
print("no site/index.html — run scripts/build-docs-site.py")
return 1
p = Anchors()
p.feed(idx.read_text(encoding="utf-8", errors="replace"))
broken: list[str] = []
for h in sorted(set(p.hrefs)):
if h.startswith("#") or h.startswith("mailto:"):
continue
if h.startswith("https://git.georgelambert.org"):
continue
if h.startswith("https://") and not h.startswith(WEB):
continue
if h.startswith(WEB):
if not local_ok(h):
broken.append(f"homepage {h}")
continue
rel = h.lstrip("/")
dest = SITE / rel
if not dest.exists():
broken.append(f"homepage missing {h}")
from pypdf import PdfReader
n_file = 0
for pdf in SITE.rglob("*.pdf"):
try:
reader = PdfReader(str(pdf))
except Exception as exc:
broken.append(f"unreadable {pdf.relative_to(SITE)} {exc}")
continue
for page in reader.pages:
annots = page.get("/Annots")
if not annots:
continue
for annot in annots:
obj = annot.get_object()
action = obj.get("/A")
if not action:
continue
uri = action.get("/URI")
if not uri:
continue
raw = str(uri)
if raw.startswith("file:"):
n_file += 1
broken.append(f"file URI {pdf.relative_to(SITE)} -> {raw[:120]}")
continue
if raw.startswith(WEB):
path = raw.split("#", 1)[0]
if "/research/" in path:
continue
if not local_ok(path):
broken.append(f"pdf missing {pdf.relative_to(SITE)} -> {raw}")
print(f"file:// leftover {n_file}")
print(f"broken {len(broken)}")
for b in broken[:80]:
print(" ", b)
if len(broken) > 80:
print(f"{len(broken) - 80} more")
return 1 if broken else 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -11,8 +11,9 @@ function Link(el)
if not path then if not path then
path, frag = t, "" path, frag = t, ""
end end
if path:match("%.md$") or path:match("%.html$") then -- PDFs should follow other PDFs for markdown sources. Keep .html (indexes, sphinx).
path = path:gsub("%.md$", ".pdf"):gsub("%.html$", ".pdf") if path:match("%.md$") then
path = path:gsub("%.md$", ".pdf")
el.target = path .. frag el.target = path .. frag
end end
return el return el