From db3a4a587042235498b14837b9ce9b4ca30c39c1 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 22 Oct 2026 02:14:07 +0300 Subject: [PATCH 1/5] Document Idempotency-Key replay of the original response The API replays a completed request's status and body for 1 hour on Messaging writes and campaign lifecycle actions, and answers 422 idempotency_conflict for key reuse. Update the README, command research notes and flag help text. Co-Authored-By: Claude Opus 5.5 --- README.md | 28 ++++++++++++++------------- docs/command-research/campaigns.md | 4 ++-- src/commands/campaign/pause.ts | 2 +- src/commands/campaign/resume.ts | 2 +- src/commands/campaign/stop.ts | 2 +- src/commands/channel/message/react.ts | 2 +- src/commands/cloud/template/create.ts | 2 +- src/commands/cloud/template/delete.ts | 2 +- 8 files changed, 23 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4c14a508..ea61abfa 100644 --- a/README.md +++ b/README.md @@ -394,20 +394,22 @@ polymorfa message send text \ --body 'Your order shipped.' --idempotency-key order-123-shipped ``` -For 24 hours after the first request, the API prevents the same key and request +For 1 hour after the first request, the API prevents the same key and request from running the write again: -- A recorded success returns `409 idempotency_completed` with - `Idempotent-Replayed: true`. The CLI reports an error; the response does not - contain the original message or response body. -- A recorded server error returns its status and error code with - `Idempotent-Replayed: true`. The write may have happened. +- A finished request returns its original status and body, success or error, + with `Idempotent-Replayed: true`. A send whose response was lost prints the + original message. +- A recorded error is returned again. Correct the request and use a new key. - A request still running returns `409 idempotency_in_progress` with `Retry-After`. An unrecorded outcome returns `409 idempotency_outcome_unknown` and blocks another attempt with that key. -- Reusing a key for a changed request returns `409 idempotency_conflict`. +- If the recorded response cannot be read right now, the API returns + `503 idempotency_replay_unavailable` with `Retry-After`. Retry with the same + key; the write does not run again. +- Reusing a key for a changed request returns `422 idempotency_conflict`. -Keep the first response when you receive it. If the outcome is uncertain, check +If the outcome is uncertain, check message events or webhooks before deciding whether to use a new key. A new key does not resolve the uncertainty and can send a duplicate. See the [Messaging retry contract](https://docs.polymorfa.com/guides/communicate/overview#retry-sends-safely). @@ -548,8 +550,8 @@ object with `name`, `language`, `category`, and `components` from `--input`, or from stdin when it is `-`. Template names and provider IDs remain strings. Edits read `{ "components": [...] }` and return acceptance, not approval to send. Deletion requires `--yes`. Create, edit and delete send one request and never -retry. Create and delete accept `--idempotency-key`; reuse the same key after an -unknown outcome and the API answers `idempotency_completed` instead of writing +retry. Create and delete accept `--idempotency-key`; reuse the same key after a +lost response and the API returns the original response instead of writing again. List the catalog before resubmitting. The template routes need an active Official API connection, including on a Hybrid Number; a Number with only a Linked Devices connection is rejected. @@ -1716,9 +1718,9 @@ instants. List returns one complete newest-first array with no cursor. Requeue is synchronous and reports only the number moved. Create, launch, pause, resume and stop accept `--idempotency-key` to deduplicate -the same request for 24 hours. A successful request repeated with the same key -returns `409 idempotency_completed`, not the original response. Changed input -returns `409 idempotency_conflict`; an in-progress or uncertain result must be +the same request for 1 hour. A request repeated with the same key returns the +original response, success or error. Changed input returns +`422 idempotency_conflict`; an in-progress or uncertain result must be resolved before trying a new key. The SDK generates a fresh key for create and launch when the flag is omitted. Supply an explicit key from the first attempt to reuse it across CLI invocations with the same input, credential and API diff --git a/docs/command-research/campaigns.md b/docs/command-research/campaigns.md index 7135187f..bb03d10b 100644 --- a/docs/command-research/campaigns.md +++ b/docs/command-research/campaigns.md @@ -99,8 +99,8 @@ server deduplication window. The middleware is mounted after authentication, rate limits and client rules, before the campaign routes under `/messaging`. Its identity includes the credential, project, method, route, API version and key; the fingerprint includes path parameters and the canonical request body. -A repeated successful request returns `409 idempotency_completed`, not the -original response body. Changed input returns `409 idempotency_conflict`; +A repeated request returns the original response, success or error. Changed +input returns `422 idempotency_conflict`; in-flight requests return `idempotency_in_progress`, and uncertain outcomes remain fenced as `idempotency_outcome_unknown`. Do not switch keys merely to bypass an uncertain result; inspect the campaign or operation first. The SDK diff --git a/src/commands/campaign/pause.ts b/src/commands/campaign/pause.ts index 232c3685..5396e315 100644 --- a/src/commands/campaign/pause.ts +++ b/src/commands/campaign/pause.ts @@ -37,7 +37,7 @@ function lifecycleFlags() { return { 'idempotency-key': Flags.string({ description: - 'Deduplicate this request for 24 hours. A completed key returns 409 idempotency_completed.', + 'Deduplicate this request for 1 hour. A repeat with the same key returns the original response.', }), 'project-slug': Flags.string({ description: 'Project slug in the Messaging API path.', diff --git a/src/commands/campaign/resume.ts b/src/commands/campaign/resume.ts index 1f01de72..9814f735 100644 --- a/src/commands/campaign/resume.ts +++ b/src/commands/campaign/resume.ts @@ -16,7 +16,7 @@ export default class CampaignResume extends BaseCommand { static override flags = { 'idempotency-key': Flags.string({ description: - 'Deduplicate this request for 24 hours. A completed key returns 409 idempotency_completed.', + 'Deduplicate this request for 1 hour. A repeat with the same key returns the original response.', }), 'project-slug': Flags.string({ description: 'Project slug in the Messaging API path.', diff --git a/src/commands/campaign/stop.ts b/src/commands/campaign/stop.ts index a9bfeb68..245db7c0 100644 --- a/src/commands/campaign/stop.ts +++ b/src/commands/campaign/stop.ts @@ -14,7 +14,7 @@ export default class CampaignStop extends BaseCommand { static override flags = { 'idempotency-key': Flags.string({ description: - 'Deduplicate this request for 24 hours. A completed key returns 409 idempotency_completed.', + 'Deduplicate this request for 1 hour. A repeat with the same key returns the original response.', }), 'project-slug': Flags.string({ description: 'Project slug in the Messaging API path.', diff --git a/src/commands/channel/message/react.ts b/src/commands/channel/message/react.ts index 1a85d3f3..8d725018 100644 --- a/src/commands/channel/message/react.ts +++ b/src/commands/channel/message/react.ts @@ -19,7 +19,7 @@ export default class ChannelMessageReact extends BaseCommand { static override flags = { 'idempotency-key': Flags.string({ description: - 'Key that makes retries of this write safe for 24 hours. Generated when omitted.', + 'Key that makes retries of this write safe for 1 hour. Generated when omitted.', }), reaction: Flags.string({ description: 'Reaction text, at most 32 UTF-8 bytes; empty removes it.', diff --git a/src/commands/cloud/template/create.ts b/src/commands/cloud/template/create.ts index 273a3d39..c71a2312 100644 --- a/src/commands/cloud/template/create.ts +++ b/src/commands/cloud/template/create.ts @@ -20,7 +20,7 @@ export default class CloudTemplateCreate extends BaseCommand { }), 'idempotency-key': Flags.string({ description: - 'Key that fences a manual retry: reuse it after an unknown outcome and the API returns idempotency_completed instead of writing again.', + 'Key that fences a manual retry: reuse it after a lost response and the API returns the original response instead of writing again.', }), } async run(): Promise { diff --git a/src/commands/cloud/template/delete.ts b/src/commands/cloud/template/delete.ts index d49a6020..3ac1e1e0 100644 --- a/src/commands/cloud/template/delete.ts +++ b/src/commands/cloud/template/delete.ts @@ -23,7 +23,7 @@ export default class CloudTemplateDelete extends BaseCommand { }), 'idempotency-key': Flags.string({ description: - 'Key that fences a manual retry: reuse it after an unknown outcome and the API returns idempotency_completed instead of writing again.', + 'Key that fences a manual retry: reuse it after a lost response and the API returns the original response instead of writing again.', }), } async run(): Promise { From 76b63696468327bb97785fc1733278b44a503661 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 5 Nov 2026 04:49:20 +0200 Subject: [PATCH 2/5] Record the replay API and SDK source binding --- docs/command-research/idempotency-replay.md | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/command-research/idempotency-replay.md diff --git a/docs/command-research/idempotency-replay.md b/docs/command-research/idempotency-replay.md new file mode 100644 index 00000000..a5c3ab22 --- /dev/null +++ b/docs/command-research/idempotency-replay.md @@ -0,0 +1,23 @@ +# Idempotency replay integration + +This guidance binds to backend source +`8aded38f41f27252df383fe2ac83e8f2f782c773` in +[API #890](https://github.com/polymorfa/polymorfa/pull/890) and the exact +consumer work in [SDK #369](https://github.com/polymorfa/sdks/pull/369). +It adds no automatic retry, key rearm or new provider/financial action. + +All eight remaining Messaging and campaign help descriptions now use the +one-hour receipt window. A recorded error does not prove the write had no +effect; callers keep the same key and reconcile events or the resource. +Sensitive response retention remains held. The first response is unchanged, +and replay-unavailable leaves the durable execution fence intact. + +The actual CLI reference producer completed after integrating current main and +building the current commands. Its inventory contains 381 commands. The human +waived extra local validation on November 5; no new local regression pass is +claimed. Hosted CI and exhaustive code/security reviews remain required. + +API source compatibility, SDK source and error recognition, CLI help/reference, +canonical EN/PT Docs and marketing are coordinated across the existing family. +Source merge does not establish deployed API or published package availability. +No Admin capability, MCP, SQL, provider, secret, flag or deployment changed. From f5df1c7198c3782a260f67a83acb66a730bf6cf3 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 5 Nov 2026 04:55:23 +0200 Subject: [PATCH 3/5] Bind CLI replay guidance to the corrected API source --- docs/command-research/idempotency-replay.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/command-research/idempotency-replay.md b/docs/command-research/idempotency-replay.md index a5c3ab22..29f0668e 100644 --- a/docs/command-research/idempotency-replay.md +++ b/docs/command-research/idempotency-replay.md @@ -1,7 +1,7 @@ # Idempotency replay integration This guidance binds to backend source -`8aded38f41f27252df383fe2ac83e8f2f782c773` in +`ec2b28bd47d210e14bb2a488df46b45b19cc317f` in [API #890](https://github.com/polymorfa/polymorfa/pull/890) and the exact consumer work in [SDK #369](https://github.com/polymorfa/sdks/pull/369). It adds no automatic retry, key rearm or new provider/financial action. From 14afb8cf3534cd493273e4efdbb2981cc94a0294 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 5 Nov 2026 05:15:50 +0200 Subject: [PATCH 4/5] Fix the production matcher audit without downgrading the CLI framework --- package-lock.json | 38 +++++++++++++++++++++++--------------- package.json | 5 ++++- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd46807f..c7271e17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,13 @@ { - "lockfileVersion": 3, "name": "@polymorfa/cli", + "version": "0.1.0-dev.0", + "lockfileVersion": 3, + "requires": true, "packages": { "": { - "bin": { - "polymorfa": "bin/run.js" - }, + "name": "@polymorfa/cli", + "version": "0.1.0-dev.0", + "license": "MIT", "dependencies": { "@modelcontextprotocol/server": "2.3.1", "@napi-rs/keyring": "1.3.0", @@ -14,6 +16,9 @@ "jq-wasm": "3.0.0-jq-1.8.2", "zod": "4.6.7" }, + "bin": { + "polymorfa": "bin/run.js" + }, "devDependencies": { "@eslint/js": "^9.39.5", "@oclif/test": "^4.1.22", @@ -28,10 +33,7 @@ }, "engines": { "node": ">=22" - }, - "license": "MIT", - "name": "@polymorfa/cli", - "version": "0.1.0-dev.0" + } }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.2", @@ -700,6 +702,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/nonrecursive-glob-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz", + "integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==", + "license": "BlueOak-1.0.0" + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -2631,11 +2639,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.1.tgz", + "integrity": "sha512-t+Vn6DX4wPCxS23y03C0uhF7S1jpi1GfqPrnlEipCJaA2ykiNbMkVlDutZtmvLfRcUN6R8AYVlzTsQrT4RZoLQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.8" + "@isaacs/nonrecursive-glob-match": "^1.0.1", + "brace-expansion": "^5.0.12" }, "engines": { "node": "18 || 20 || >=22" @@ -3920,7 +3930,5 @@ "url": "https://github.com/sponsors/colinhacks" } } - }, - "requires": true, - "version": "0.1.0-dev.0" + } } diff --git a/package.json b/package.json index 0a9df267..29439c13 100644 --- a/package.json +++ b/package.json @@ -352,5 +352,8 @@ "verify:browser": "node scripts/verify-browser.mjs" }, "type": "module", - "version": "0.1.0-dev.0" + "version": "0.1.0-dev.0", + "overrides": { + "minimatch@>=7.0.0 <11.0.1": "11.0.1" + } } From 32dfef3f25b0bf0e8d539c77645315072d6533c5 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 5 Nov 2026 05:52:31 +0200 Subject: [PATCH 5/5] Update replay integration evidence for checked API source --- docs/command-research/idempotency-replay.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/command-research/idempotency-replay.md b/docs/command-research/idempotency-replay.md index 29f0668e..6f61789f 100644 --- a/docs/command-research/idempotency-replay.md +++ b/docs/command-research/idempotency-replay.md @@ -1,7 +1,7 @@ # Idempotency replay integration This guidance binds to backend source -`ec2b28bd47d210e14bb2a488df46b45b19cc317f` in +`43a286d2133c2328eb30d6210572d9bfc19734f0` in [API #890](https://github.com/polymorfa/polymorfa/pull/890) and the exact consumer work in [SDK #369](https://github.com/polymorfa/sdks/pull/369). It adds no automatic retry, key rearm or new provider/financial action. @@ -13,9 +13,12 @@ Sensitive response retention remains held. The first response is unchanged, and replay-unavailable leaves the durable execution fence intact. The actual CLI reference producer completed after integrating current main and -building the current commands. Its inventory contains 381 commands. The human -waived extra local validation on November 5; no new local regression pass is -claimed. Hosted CI and exhaustive code/security reviews remain required. +building the current commands. Its inventory contains 381 commands. Before the dependency-security fix was +committed, the full local verification passed: formatting, lint, typechecking, +118 test files with 1,164 tests, build, reference and browser checks, package +dry run and a production audit with zero vulnerabilities. This source-binding +update changes only this internal record; it changes no command or package +behavior. Hosted CI and exhaustive code/security reviews remain required. API source compatibility, SDK source and error recognition, CLI help/reference, canonical EN/PT Docs and marketing are coordinated across the existing family.