From 648bd13fdd9cdcbc73833e02f609987f555730d3 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 26 Sep 2026 23:01:01 +0200 Subject: [PATCH 1/2] fix(sdk): submit project templates in one attempt --- contracts/README.md | 7 +++++++ contracts/cloud-api-supplements.json | 8 ++++++++ packages/typescript/README.md | 9 +++++++++ packages/typescript/src/messaging/templates.ts | 2 ++ 4 files changed, 26 insertions(+) diff --git a/contracts/README.md b/contracts/README.md index fe5bb333..d554da24 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -64,6 +64,13 @@ pattern correction; their request types and method mappings are unchanged. `source.json` records the source paths and SHA-256 hashes. API merge, SDK publication and deployed availability remain separate. +API PR #377 at `0f559ed7f89227fa220d6a0275fcc40b8e319a55` adds local +validation before project-template submission. Its exact OpenAPI source and hash +are recorded in `cloud-api-supplements.json`. The existing typed +`MessagingClient.templates.submit` method now makes one network attempt, +including when callers supply an idempotency key or retry override. The full +snapshot remains pinned to PR #310 until the API branches converge. + The merged Campaigns failed-state follow-up updates the `campaign.failed` reason example and the public archive/delete `409` descriptions. It changes no operation fingerprint or SDK method mapping. diff --git a/contracts/cloud-api-supplements.json b/contracts/cloud-api-supplements.json index baa7fd3a..a9a32129 100644 --- a/contracts/cloud-api-supplements.json +++ b/contracts/cloud-api-supplements.json @@ -29,6 +29,14 @@ "operationIds": ["editTemplate"], "merged": false }, + { + "pullRequest": "https://github.com/polymorfa/polymorfa/pull/377", + "commit": "0f559ed7f89227fa220d6a0275fcc40b8e319a55", + "sourcePath": "apps/api/docs/openapi.json", + "sha256": "7d2c6060f054eab6d4c60c9c58da41cc6f7282bc86c5098c234ec2a9dd3d0c8c", + "operationIds": ["submitProjectTemplateToMeta"], + "merged": false + }, { "pullRequest": "https://github.com/polymorfa/polymorfa/pull/348", "commit": "d22930d2453c3ae428fac807f545277bb748594c", diff --git a/packages/typescript/README.md b/packages/typescript/README.md index d4a3c4f2..659ad32e 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -305,10 +305,19 @@ const created = await messaging.templates.create("support", { await messaging.templates.preview("support", created.data.data.id, { values: { name: "Grace" }, }); + +await messaging.templates.submit("support", created.data.data.id, { + session: "number_123", +}); ``` Keep this client on the server. Browser builders use an application-owned route, such as `createTemplateBuilderRoute` from `@polymorfa/nextjs`. +Submission validates the saved definition before making one provider attempt. +A successful response records the submission; it does not establish approval. +If the request times out or loses its response, read the Number's template +catalog before deciding whether another submission is needed. Local preview +does not ask Meta to validate the template. ## Flow drafts and provider lifecycle diff --git a/packages/typescript/src/messaging/templates.ts b/packages/typescript/src/messaging/templates.ts index 3cec7d87..ab317f69 100644 --- a/packages/typescript/src/messaging/templates.ts +++ b/packages/typescript/src/messaging/templates.ts @@ -90,6 +90,7 @@ export class TemplatesResource { }); } + /** Submit once. If the response is lost, read the Number's template catalog before another attempt. */ submit( projectSlug: string, templateId: string, @@ -101,6 +102,7 @@ export class TemplatesResource { path: `${templatePath(projectSlug, templateId)}/submit`, body, ...options, + maxNetworkRetries: 0, }); } } From 5dd6c8e4866f267398f9dfcffbfc466bdacfc780 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 1 Oct 2026 15:17:15 +0300 Subject: [PATCH 2/2] Test single-attempt project template submission --- .../typescript/test/cloud-templates.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/typescript/test/cloud-templates.test.ts b/packages/typescript/test/cloud-templates.test.ts index 951051a2..3d768cd8 100644 --- a/packages/typescript/test/cloud-templates.test.ts +++ b/packages/typescript/test/cloud-templates.test.ts @@ -163,6 +163,29 @@ describe("Cloud templates", () => { }, ); + it("submits a project template draft to Meta once, even with a retry override", async () => { + const server = await startTestServer(() => ({ + status: 500, + body: JSON.stringify({ + error: { code: "internal_error", message: "Outcome unknown" }, + }), + })); + servers.push(server); + await expect( + client(server).templates.submit( + "support", + "tpl/1", + { session: "number_123" }, + { idempotencyKey: "customer-key", maxNetworkRetries: 3 }, + ), + ).rejects.toMatchObject({ status: 500 }); + expect(server.requests).toHaveLength(1); + expect(server.requests[0]!.method).toBe("POST"); + expect(server.requests[0]!.path).toBe( + "/messaging/projects/support/templates/tpl%2F1/submit", + ); + }); + it("returns a catalog failure instead of an empty successful list", async () => { const server = await startTestServer(() => ({ status: 502,