Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Integration checks
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
contracts-and-adapters:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: .nvmrc
cache: npm
- run: npm install --global npm@11.20.0
- run: npm ci --ignore-scripts --no-audit
- run: npm run check
- run: npm run lint --workspace n8n-nodes-polymorfa
- run: npm run validate:zapier
- run: npm run check:sdk
- run: node scripts/check-upstream.mjs --pinned
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22.23.2'
- run: node --test --test-reporter=spec tests/*.test.cjs tests/*.test.mjs
85 changes: 85 additions & 0 deletions .github/workflows/publish-n8n.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Publish n8n package
on:
workflow_dispatch:
Comment thread
purpshell marked this conversation as resolved.
Resolved
inputs:
version:
description: Reviewed package version
required: true
type: string
reviewed-source:
description: Immutable reviewed source commit from the approved release receipt
required: true
type: string
permissions:
contents: read
jobs:
verify:
# Runs repository code, so it never receives an OIDC token.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
ref: ${{ github.sha }}
persist-credentials: false
# Gate first: nothing else from the repository runs before the source binding is checked.
- run: node scripts/release-gate.mjs
env:
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_REVIEWED_SOURCE: ${{ inputs.reviewed-source }}
RELEASE_REF: ${{ github.ref }}
RELEASE_SOURCE: ${{ github.sha }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: .nvmrc
- run: npm install --global npm@11.20.0
- run: npm ci --ignore-scripts --no-audit
- run: npm run check
- run: npm run lint --workspace n8n-nodes-polymorfa
- run: npm run check:sdk
publish:
needs: verify
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: npm-release
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
ref: ${{ github.sha }}
persist-credentials: false
# Git and jq only. This runs before any repository file is executed or read as configuration
# (.nvmrc, .npmrc, package scripts), because this job can mint an npm OIDC token.
- name: Check reviewed source binding
shell: bash
env:
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_REVIEWED_SOURCE: ${{ inputs.reviewed-source }}
RELEASE_REF: ${{ github.ref }}
RELEASE_SOURCE: ${{ github.sha }}
run: |
set -euo pipefail
[[ "$RELEASE_REF" == refs/heads/main ]]
[[ "$RELEASE_SOURCE" =~ ^[a-f0-9]{40}$ && "$RELEASE_REVIEWED_SOURCE" =~ ^[a-f0-9]{40}$ ]]
[[ "$(git rev-parse HEAD)" == "$RELEASE_SOURCE" ]]
[[ "$RELEASE_VERSION" =~ ^[0-9A-Za-z.+-]+$ ]]
[[ "$(git show HEAD:n8n/package.json | jq -r .version)" == "$RELEASE_VERSION" ]]
receipt="$(git show "HEAD:releases/$RELEASE_VERSION.json" | jq -r .reviewedSource)"
[[ "$receipt" == "$RELEASE_REVIEWED_SOURCE" ]]
git merge-base --is-ancestor "$RELEASE_REVIEWED_SOURCE" HEAD
# Everything except release receipts must match the reviewed source.
git diff --exit-code "$RELEASE_REVIEWED_SOURCE" HEAD -- . ':(exclude)releases'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: .nvmrc
registry-url: https://registry.npmjs.org
- run: npm install --global npm@11.20.0
- run: npm ci --ignore-scripts --no-audit
- run: npm run build
- run: npm publish --workspace n8n-nodes-polymorfa --provenance --access public
16 changes: 16 additions & 0 deletions .github/workflows/sdk-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: SDK drift
on:
workflow_dispatch:
schedule:
- cron: '23 8 * * 1'
permissions:
contents: read
jobs:
check-reviewed-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: .nvmrc
- run: node scripts/check-upstream.mjs
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
save-exact=true
fund=false
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.15.0
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Changelog

## Unreleased

- Separate webhook receiver secrets from public marker/idempotency metadata.
- Remove the Webhook Creation Key field from all triggers. Each activation uses
its own adapter-generated nonce, so off/on, n8n test listening and Pipedream
redeploys work, and a receipt for a deleted webhook is refused.
- Make: detach reads the recorded webhook by ID; unusual URLs on unrelated
project webhooks no longer break attach.
- Zapier: accept the documented `Http-` prefixed raw request header form.
- n8n Continue On Fail items include `code`, `status`, `requestId` and
`retryAfter`. Server errors on writes keep the reconcile guidance.
- The n8n inbound handoff recipe derives its subscription key from the workflow ID.
- Preserve definite subscription errors and non-JSON uncertain outcomes;
normalize callback ownership and native Zapier response headers.
- Keep successful n8n batch items when Continue On Fail is enabled. Require
saved notification identities before recipe sends and write identities in Zapier.
- Restrict n8n publication dispatch to main and bind its reviewed source receipt.

- First-party source for n8n, Make, Zapier and Pipedream with a common public API
and SDK contract pin.
- Text/replies, templates, URL/base64 media, reactions, Number/template lookup
and allowlisted API calls. n8n also maps native binary inputs.
- Owned project-webhook subscriptions for messages, message state, Number state
and completed calls, with authenticated receivers and stable event identity.
- Explicit recovery for completed/uncertain writes and conservative retry age.
- Contract/generation checks, SDK drift detection and provider release records.

This is development source. No registry publication, provider approval or public
marketplace listing is established. Functions and message-operation lookup are
excluded until their public contracts are accepted.
98 changes: 98 additions & 0 deletions MAINTAINING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Maintain the integrations with the SDKs

Use Node 24.15.0 and npm 11.20.0 for the build tools. The generated JavaScript is
also checked on Node 22.23.2 for the Zapier execution boundary. These checks do
not establish the versions deployed by a provider.

```sh
npm ci --ignore-scripts
npm run check
npm run lint --workspace n8n-nodes-polymorfa
npm run validate:zapier
npm run check:sdk
node scripts/check-upstream.mjs --pinned
```

`shared/` owns the action fields, event groups, request mapping and recovery
behavior. `npm run generate` copies that code into independently deployable
native packages and generates Make functions and Pipedream components. Do not
edit generated files directly. CI rejects stale generated output, including
files in generated directories that the generator no longer emits; `npm run
generate` removes them.

`contracts/source.json` pins the public API audience and matching SDK source.
`coverage.json` identifies each allowed operation and deliberate exclusions.
The public snapshots contain only these operations and their referenced schemas.
Do not import the complete management API or an SDK's extra operation catalog.

The SDK's baseline also contains fields outside this public audience, including
transport selection. The adapters do not expose them. `check:sdk` validates the
actual request cases against the pinned SDK schemas. `sdk-baseline.json` records
the corresponding reviewed SDK fragment fingerprints. `check:upstream` compares
them to current SDK `main`; its scheduled workflow reports changes without
repinning, publishing or creating an app. The pinned check is reproducible even
when SDK `main` advances.

For a relevant API/SDK change, update the accepted public snapshot, source/hash
metadata, coverage and every affected adapter. Add an exclusion with its reason
when a capability is intentionally unavailable. Test errors, IDs, media, hooks,
pagination and uncertain writes where affected. Regenerate all packages and
update native recipe mappings. Keep one integrations PR for that feature cycle.

Record real provider tests separately from these local fixtures. Include exact
source/app versions and the accepted API revision. Update the owning product
feature, issue, release record, customer documentation and provider submission.
Source merge, package publication, app review and public listing are distinct.

The n8n publication workflow is manual, uses npm provenance and requires recorded
receipts. Configure a protected `npm-release` environment and a Polymorfa-owned
npm trusted publisher before use. No provider account, environment protection or
trusted publisher has been configured by adding this workflow. Review is still
required; the gate is bookkeeping and does not authenticate human authorization.

Publication accepts only a `main` dispatch, checks out its immutable commit, and
requires the dispatch's reviewed-source commit to match the release receipt. Every
path except `releases/` must match the reviewed source, including tests, `.npmrc`
and `.nvmrc`. The `verify` job runs the gate before any install or test and has no
OIDC token. The `publish` job, which has `id-token: write`, repeats the check with
git and jq before Node setup, `.npmrc` use or any repository script. Restrict the `npm-release` environment to protected `main`
and require an independent release reviewer before enabling the trusted publisher.
These external settings cannot be secured by editable workflow code alone.

Webhook marker names, receiver verifiers and create-key seeds use separate HMAC
domains. The create key is `HMAC(markerKey, nonce)`. `hooks.activate` generates
a random nonce per activation and saves it in provider-private state before the
create: n8n node static data, Pipedream `$.service.db`, Zapier and Make in the
single `performSubscribe` call or attach run (neither platform offers state that
persists before those return). An uncertain retry of the same activation reuses
the nonce; a confirmed activation, a deletion or a replayed receipt for a deleted
webhook (`subscription_receipt_deleted`) clears it. Find-before-create and the
post-create live-ownership check stay in place. Users no longer enter a webhook
creation key. This is a change before the first release; no migration is implied.

Keep API credentials in each provider's credential store. Webhook receivers use
an authenticated per-subscription header supported by the public subscription
contract. A stable unique subscription key determines its private verifier.
Reactivation reuses its owned hook; another callback cannot reuse the key while
that hook exists. Changing a verifier or connection requires explicit lifecycle
and rotation testing. The adapters never emit signing secrets or add a relay.

All delivery channels can repeat events. Use the event ID in downstream dedupe
storage and a stable business key for effects. Zapier and Pipedream also use that
event ID in their native dedupe paths. No unbounded exactly-once claim is made.
Native n8n and Make consumers must persist their own processed-event IDs.

Revoking an API key does not delete an existing webhook endpoint. Disable/delete
the owned endpoint before withdrawing a connection, or remove it through an
authorized project management path if the key is already revoked. Never use a
broader credential automatically. Verify provider log redaction and callback
cleanup during acceptance, including failed cleanup and credential rotation.

Make uses provider-native custom IML functions, which require enablement by Make.
The generated JavaScript functions are tested, but their IML bindings, private
webhook-state mapping and attach/detach behavior require real platform acceptance.
Its attach preflight refuses inventories larger than one page instead of risking
a duplicate. Detach reads the recorded webhook by ID, so cleanup works in any
project size. Qualify attach pagination in Make before approving large-project
triggers. Its hook matching treats an unparseable URL as no match, as the shared
code does; a test runs both over the same inventories.
5 changes: 5 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The Polymorfa marks identify Polymorfa's service. The n8n icon uses the approved
Polymorfa website favicon. Trademark rights are separate from the MIT code license.

n8n, Make, Zapier and Pipedream are names of their respective providers. This
development repository does not claim provider certification or approval.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,22 @@ a separate workflow engine or stores your third-party app connections.

Documentation: [docs.polymorfa.com](https://docs.polymorfa.com).

Webhook triggers ask for one value: a subscription key that identifies the
trigger and stays stable. Each activation registers its own webhook with a
creation nonce the adapter generates and keeps in the platform's private trigger
state. A retry of an uncertain activation reuses that nonce, so it cannot create
a second webhook. Turning a trigger off and on again uses a new nonce. Before
creating, every adapter looks for its owned webhook and adopts it, and after
creating it confirms the webhook is live. If the API replays a receipt for a
webhook that was since deleted, activation fails with
`subscription_receipt_deleted` instead of reporting a working trigger; the next
activation registers a new webhook. Native editor acceptance remains pending.

Build and maintenance: [MAINTAINING.md](MAINTAINING.md).
Provider source: [n8n](n8n/README.md), [Make](make/README.md),
[Zapier](zapier/README.md), [Pipedream](pipedream/README.md).
[Recipe candidates](recipes/README.md) and [release state](releases/0.1.0.json)
record the remaining native acceptance and publication work.

License: MIT. Newly authored connector code only; no archived Titan adapter code
is included.
86 changes: 86 additions & 0 deletions contracts/coverage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"operations": {
"sendMessage": {
"contract": "messaging",
"method": "POST",
"path": "/messaging/{session}/messages/send",
"scope": "messages:write",
"sdkOperation": "sendMessage",
"purpose": "action"
},
"sendReaction": {
"contract": "messaging",
"method": "POST",
"path": "/messaging/{session}/messages/react",
"scope": "messages:write",
"sdkOperation": "sendReaction",
"purpose": "action"
},
"listProjectTemplates": {
"contract": "messaging",
"method": "GET",
"path": "/messaging/projects/{projectSlug}/templates",
"scope": "templates:read",
"sdkOperation": "listProjectTemplates",
"purpose": "action"
},
"getProjectTemplate": {
"contract": "messaging",
"method": "GET",
"path": "/messaging/projects/{projectSlug}/templates/{id}",
"scope": "templates:read",
"sdkOperation": "getProjectTemplate",
"purpose": "action"
},
"listPlatformSessions": {
"contract": "platform",
"method": "GET",
"path": "/platform/sessions",
"scope": "sessions:read",
"sdkOperation": "listPlatformSessions",
"purpose": "action"
},
"listProjectWebhooks": {
"contract": "platform",
"method": "GET",
"path": "/platform/projects/{projectId}/webhooks",
"scope": "webhooks:read",
"sdkOperation": "listProjectWebhooks",
"purpose": "subscription lifecycle"
},
"createProjectWebhook": {
"contract": "platform",
"method": "POST",
"path": "/platform/projects/{projectId}/webhooks",
"scope": "webhooks:manage",
"sdkOperation": "createProjectWebhook",
"purpose": "subscription lifecycle"
},
"getProjectWebhook": {
"contract": "platform",
"method": "GET",
"path": "/platform/projects/{projectId}/webhooks/{webhookId}",
"scope": "webhooks:read",
"sdkOperation": "getProjectWebhook",
"purpose": "subscription lifecycle"
},
"deleteProjectWebhook": {
"contract": "platform",
"method": "DELETE",
"path": "/platform/projects/{projectId}/webhooks/{webhookId}",
"scope": "webhooks:manage",
"sdkOperation": "deleteProjectWebhook",
"purpose": "subscription lifecycle"
}
},
"platforms": [
"n8n",
"make",
"zapier",
"pipedream"
],
"excluded": {
"getMessageOperation": "Absent from published public Messaging export; SDK presence is insufficient.",
"runFunction": "Functions deliberately absent from published public Platform export."
}
}
Loading
Loading