From 7f7fd3e9132d3b31d0e2e6a0d8f85f6eda461e15 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 17 Oct 2026 21:13:53 +0300 Subject: [PATCH 1/2] fix: remove organization key and project token management resources --- README.md | 6 +- contracts/README.md | 14 +- contracts/coverage.json | 41 +-- contracts/credential-management.json | 27 ++ contracts/openapi.platform.json | 327 ++++-------------- contracts/source.json | 14 +- examples/full-platform/README.md | 2 +- .../app/api/admin/organization/route.ts | 19 +- .../full-platform/lib/desk/demo-fixtures.ts | 35 -- examples/full-platform/ui/pages/admin.tsx | 44 +-- packages/typescript/README.md | 37 +- packages/typescript/src/client.ts | 6 - packages/typescript/src/index.ts | 5 - packages/typescript/src/platform/api-keys.ts | 28 -- .../typescript/src/platform/project-tokens.ts | 19 - packages/typescript/src/platform/types.ts | 35 -- .../test/coverage-reconciliation.test.ts | 40 ++- packages/typescript/test/coverage.test.ts | 19 +- packages/typescript/test/exports.test.ts | 10 +- packages/typescript/test/package.test.ts | 6 +- .../typescript/test/platform-access.test.ts | 50 +-- 21 files changed, 184 insertions(+), 600 deletions(-) create mode 100644 contracts/credential-management.json delete mode 100644 packages/typescript/src/platform/api-keys.ts delete mode 100644 packages/typescript/src/platform/project-tokens.ts diff --git a/README.md b/README.md index 89fe9e75..1fbda0ba 100644 --- a/README.md +++ b/README.md @@ -278,13 +278,11 @@ project clients see only their project. The organization view also exposes these management resources: - `organizations`: retrieve the organization visible to the API key -- `apiKeys`: list key metadata and deactivate an organization API key - `members`: list organization members - `auditLogs`: list the organization audit trail with live action, resource, and limit filters - `sessionBans`: list all or active session bans - `securityIncidents`: list and acknowledge leaked-credential incidents -- `projectTokens`: list token metadata for an explicit project - `sipTrunks`: list, create, retrieve, update, delete, and rotate the credentials of a project's SIP trunks, and read the SIP address your PBX points at with `endpoint()` (also on project clients) @@ -962,3 +960,7 @@ quiet gaps, duration sums, sample counts and weighted averages. Ranges up to 48 hours use hourly activity buckets; longer ranges and call series use daily UTC buckets. These signals do not establish phone online/offline or power state. See the [measurement definitions](https://docs.polymorfa.com/console/analytics#phone-polymorfa-and-customer-activity). + +Organization keys and project tokens are created, listed and revoked by a signed-in +person in the Console. The SDK exposes client-token operations only; machine +credentials cannot manage organization keys or project tokens. diff --git a/contracts/README.md b/contracts/README.md index 5265981d..d0d3c7bf 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,3 +1,11 @@ +## Human-only credential-management supplement + +The Platform snapshot removes GET /platform/keys, DELETE /platform/keys/{keyId} +and GET /platform/tokens. The full baseline remains recorded below; the exact +removal source revision is recorded in credential-management.json. No unrelated +contract parity or registry availability is implied. Signed-in Console people +manage organization keys and project tokens. Client-token operations remain. + # Contract coverage ## Message provider references @@ -41,7 +49,7 @@ published SDK package before updating their pinned dependency. ## Full snapshots -The current snapshots are byte-identical copies of the Messaging and Platform +The baseline snapshots are byte-identical copies of the Messaging and Platform contracts at API dev merge commit `e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb` (after polymorfa/polymorfa#330, #328, #335, #382, #408, #494, #482 and #488). Campaign create, update and launch types add template variable mappings, @@ -50,8 +58,8 @@ add sequence progress; webhooks add `campaign.recipient_link_opened` and `messageIndex`. Fourteen new Campaigns operations (test send, audience profiles and imports, link reports, campaign settings and approval reads) are recorded as missing until their methods follow. The three approval decisions and the -threshold update are console routes and are excluded. The ledger contains 608 -operations: 454 covered, 0 partial, 14 missing and 140 excluded. +threshold update are console routes and are excluded. The ledger after the human-only credential-management removal contains 605 +operations: 451 covered, 0 partial, 14 missing and 140 excluded. The previous snapshots were byte-identical copies of the Messaging and Platform contracts at API dev merge commit `ff12a76fe44fa23bf71e00fc6454a51b1fdbb399` diff --git a/contracts/coverage.json b/contracts/coverage.json index bc2a3e4e..ed178eab 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -2778,17 +2778,6 @@ "method": "Client.project(projectId).functions.secrets.revoke" } }, - { - "family": "platform", - "method": "DELETE", - "path": "/platform/keys/{keyId}", - "operationId": "deactivateApiKey", - "fingerprint": "3bc6580c15162209c02f62d80fce46f3860eeaeab4cdcf762462575611403286", - "typescript": { - "status": "covered", - "method": "Client.apiKeys.deactivate" - } - }, { "family": "platform", "method": "DELETE", @@ -3352,7 +3341,7 @@ }, "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb", - "sdkContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b" + "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5" }, { "family": "platform", @@ -3365,7 +3354,7 @@ }, "fingerprint": "e999dce00d84dd7b4d40958ea76527c37c27fc867fa7ccf5f9f4e713c1f12555", "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", - "sdkContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", + "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb" }, { @@ -4145,17 +4134,6 @@ "method": "Client.securityIncidents.list" } }, - { - "family": "platform", - "method": "GET", - "path": "/platform/keys", - "operationId": "listApiKeys", - "fingerprint": "9ee9f6dd215383112136a81a368df55ac054ef468daa9fd200a5824f27ad3b16", - "typescript": { - "status": "covered", - "method": "Client.apiKeys.list" - } - }, { "family": "platform", "method": "GET", @@ -4278,7 +4256,7 @@ }, "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb", - "sdkContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b" + "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5" }, { "family": "platform", @@ -4291,7 +4269,7 @@ }, "fingerprint": "240ac51bf9ba12d47624b0cd8ba96b018d1b2e0e7e7e4e8e4d8c3152025c4fab", "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", - "sdkContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", + "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb" }, { @@ -4670,17 +4648,6 @@ "milestone": "credential-boundary" } }, - { - "family": "platform", - "method": "GET", - "path": "/platform/tokens", - "operationId": "listPolymorfaTokens", - "fingerprint": "d8ab97e205775112f019858dee537c0b36a65987eb8d003b1681910572e50473", - "typescript": { - "status": "covered", - "method": "Client.projectTokens.list" - } - }, { "family": "platform", "method": "GET", diff --git a/contracts/credential-management.json b/contracts/credential-management.json new file mode 100644 index 00000000..a9fa31e6 --- /dev/null +++ b/contracts/credential-management.json @@ -0,0 +1,27 @@ +{ + "repository": "polymorfa/polymorfa", + "sourceCommit": "cdc3d8dd2770693f1d319ecf5d0fa1a058717a0d", + "sourcePath": "apps/api/docs/openapi.management.json", + "sourceContractSha256": "dd02c846c218772a62539781764726bc366ff6047597f9150112225022f65471", + "removedOperations": [ + { + "method": "GET", + "path": "/platform/keys" + }, + { + "method": "DELETE", + "path": "/platform/keys/{keyId}" + }, + { + "method": "GET", + "path": "/platform/tokens" + } + ], + "retainedHumanRoutes": [ + "/console/keys", + "/console/keys/{keyId}", + "/api/account/keys", + "/api/account/projects/{projectId}/tokens" + ], + "note": "Applies only this credential-management removal to the existing full baseline. Client-token operations are unchanged. This candidate does not assert unrelated API parity, publication or deployment." +} diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index 44c416a9..35254d14 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (267 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (114 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/\u2026` \u2014 **public registry** (267 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/\u2026` \u2014 **console-only** (114 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -960,7 +960,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -1123,7 +1123,7 @@ "x-mint": { "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" }, - "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`", + "description": "**Required scopes (all):** `sessions:manage` \u00b7 `messages:write` \u00b7 `contacts:read` \u00b7 `presence:read` \u00b7 `presence:observe` \u00b7 `mcp`", "x-audience": "public" } }, @@ -1173,7 +1173,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -1401,7 +1401,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -1564,7 +1564,7 @@ "x-mint": { "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" }, - "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`", + "description": "**Required scopes (all):** `sessions:manage` \u00b7 `messages:write` \u00b7 `contacts:read` \u00b7 `presence:read` \u00b7 `presence:observe` \u00b7 `mcp`", "x-audience": "public" }, "delete": { @@ -1612,7 +1612,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -1827,7 +1827,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -2051,7 +2051,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -2286,7 +2286,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -2497,7 +2497,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -2875,7 +2875,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -3149,7 +3149,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -3455,7 +3455,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -3690,7 +3690,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -3905,7 +3905,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -4116,7 +4116,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -4393,7 +4393,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -4616,7 +4616,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -4892,7 +4892,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -5138,7 +5138,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -5405,7 +5405,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -5616,7 +5616,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -5895,7 +5895,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -6127,7 +6127,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -6372,7 +6372,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -6618,7 +6618,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -6862,7 +6862,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -7073,7 +7073,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -7382,7 +7382,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -7724,7 +7724,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -8001,7 +8001,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -8224,7 +8224,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -8468,7 +8468,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -8714,7 +8714,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -8993,7 +8993,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -9204,7 +9204,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -9545,7 +9545,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -9758,7 +9758,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -9972,7 +9972,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -10216,7 +10216,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -10436,7 +10436,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -10882,7 +10882,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -11095,7 +11095,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -11308,7 +11308,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -11481,7 +11481,7 @@ "tags": [ "Sessions" ], - "summary": "Initiate M↔P toggle: fetch the destination Curve25519 pubkey", + "summary": "Initiate M\u2194P toggle: fetch the destination Curve25519 pubkey", "security": [ { "ApiKeyAuth": [] @@ -11521,7 +11521,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -11743,7 +11743,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -11965,7 +11965,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -12138,7 +12138,7 @@ "tags": [ "Sessions" ], - "summary": "Acknowledge receipt of an M→P export; API flips bartenderMode after hash verify", + "summary": "Acknowledge receipt of an M\u2192P export; API flips bartenderMode after hash verify", "security": [ { "ApiKeyAuth": [] @@ -12166,7 +12166,7 @@ }, "responses": { "200": { - "description": "Ack accepted; bartenderMode flipped from magic → passthrough", + "description": "Ack accepted; bartenderMode flipped from magic \u2192 passthrough", "content": { "application/json": { "schema": { @@ -12187,7 +12187,7 @@ } }, "400": { - "description": "Bad request — request body or params failed validation", + "description": "Bad request \u2014 request body or params failed validation", "content": { "application/json": { "schema": { @@ -28440,7 +28440,7 @@ "limitCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "maximum": 1000000, "nullable": true }, @@ -28674,7 +28674,7 @@ "limitCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "maximum": 1000000, "nullable": true }, @@ -37766,197 +37766,6 @@ } } }, - "/platform/keys": { - "get": { - "operationId": "listApiKeys", - "tags": [ - "keys" - ], - "summary": "List api keys", - "description": "Public Management API. Requires a team API key with `sessions:read`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", - "security": [ - { - "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/platform/keys/{keyId}": { - "delete": { - "operationId": "deactivateApiKey", - "tags": [ - "keys" - ], - "summary": "Deactivate api key", - "description": "Public Management API. Requires a team API key with `sessions:manage`. Project tokens are not accepted.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "organization", - "x-excluded-from-public": false, - "x-required-scope": "sessions:manage", - "security": [ - { - "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "keyId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, - "/platform/tokens": { - "get": { - "operationId": "listPolymorfaTokens", - "tags": [ - "tokens" - ], - "summary": "List polymorfa tokens", - "description": "Public Management API. Requires `sessions:read`. Project tokens only access resources in their own project.", - "x-audience": "public", - "x-polymorfa-resource-boundary": "project", - "x-excluded-from-public": false, - "x-required-scope": "sessions:read", - "security": [ - { - "ApiKeyAuth": [] - }, - { - "ConsoleSession": [] - } - ], - "parameters": [ - { - "name": "projectId", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "X-Request-Id": { - "schema": { - "type": "string" - }, - "description": "Stable request correlation identifier." - }, - "Polymorfa-Version": { - "schema": { - "type": "string" - }, - "description": "API version applied to this response." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DataEnvelope" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "501": { - "$ref": "#/components/responses/NotImplemented" - } - } - } - }, "/platform/audit": { "get": { "operationId": "listAuditLogs", @@ -38511,11 +38320,11 @@ "keys" ], "summary": "Mint for console api key", - "description": "**EXCLUDED from the public API** (`console`). Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", + "description": "**EXCLUDED from the public API** (`console`). Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only \u2014 keeps revocation meaningful.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", + "x-exclusion-reason": "Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only \u2014 keeps revocation meaningful.", "security": [ { "ConsoleSession": [] @@ -45276,19 +45085,19 @@ "limitCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "maximum": 1000000, "nullable": true }, "spentCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001 + "multipleOf": 1e-06 }, "reservedCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001 + "multipleOf": 1e-06 }, "revision": { "type": "integer", @@ -45350,12 +45159,12 @@ "todayCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001 + "multipleOf": 1e-06 }, "monthCredits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001 + "multipleOf": 1e-06 }, "daily": { "type": "array", @@ -45370,7 +45179,7 @@ "credits": { "type": "number", "minimum": 0, - "multipleOf": 0.000001 + "multipleOf": 1e-06 } }, "required": [ @@ -53949,7 +53758,7 @@ "type": "string", "maxLength": 80, "nullable": true, - "description": "Page title shown above the pairing flow. Defaults to “Connect WhatsApp”." + "description": "Page title shown above the pairing flow. Defaults to \u201cConnect WhatsApp\u201d." }, "description": { "type": "string", @@ -55180,7 +54989,7 @@ "amountCents": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "description": "Credits charged on application, with up to six decimal places." }, "priceVersion": { @@ -60409,19 +60218,19 @@ "measuredCents": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "description": "Credit quantity this number consumed inside the window, with up to six decimal places." }, "capCents": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "description": "Maximum credit quantity this claim can return, with up to six decimal places." }, "amountCents": { "type": "number", "minimum": 0, - "multipleOf": 0.000001, + "multipleOf": 1e-06, "description": "Credit quantity this claim returns, with up to six decimal places." }, "evidence": { @@ -67714,7 +67523,7 @@ "enum": [ "passthrough_plus" ], - "description": "When set, atomically flips sessions.config.bartenderMode → passthrough_plus after the keys persist. Eliminates the window where mode is set but no keys are staged." + "description": "When set, atomically flips sessions.config.bartenderMode \u2192 passthrough_plus after the keys persist. Eliminates the window where mode is set but no keys are staged." } }, "required": [ diff --git a/contracts/source.json b/contracts/source.json index fb3423c4..a6d95d93 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -3,7 +3,7 @@ "commit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb", "branch": "dev", "published": true, - "note": "Full Messaging and Platform snapshots pin the merged API dev revision. SDK publication and API deployment remain separate gates.", + "note": "Full snapshots retain the recorded baseline; Platform applies the separately pinned human-only credential-management removal supplement. Publication and deployment are separate gates.", "contracts": { "messaging": { "sourcePath": "apps/api/docs/openapi.json", @@ -13,7 +13,15 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b" + "sha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5" } - } + }, + "supplements": [ + { + "name": "human-only-credential-management", + "snapshotPath": "contracts/credential-management.json", + "sourceCommit": "cdc3d8dd2770693f1d319ecf5d0fa1a058717a0d", + "sha256": "bda73eec25574a37c9fa412d5052d0c163322d6b3df0fa7daafcdaa7132e3223" + } + ] } diff --git a/examples/full-platform/README.md b/examples/full-platform/README.md index 17cab10b..b9353b8f 100644 --- a/examples/full-platform/README.md +++ b/examples/full-platform/README.md @@ -204,7 +204,7 @@ flowchart LR | Theme, accent, density, locale and direction | `PolymorfaProvider`, `appearance.darkVariables`, `createLocale` | `app/providers.tsx`, `ui/pages/settings.tsx` | | Development assistant | `DevAssistant`, `mountDevAssistant` (`position`, `defaultOpen`, `offset`) | `app/providers.tsx` | | Web Components | `definePolymorfaElements`, `pmfa-message-list`, `pmfa-template-builder` | `app/elements/elements.tsx` | -| Admin: organization, members, API keys, project tokens | `Client.organizations`, `members`, `apiKeys`, `projectTokens` | `app/api/admin/organization/route.ts` | +| Admin: organization, members | `Client.organizations`, `members` | `app/api/admin/organization/route.ts` | | Admin: projects, billing, security, customers, audiences, opt-outs | `Client.projects`, `billing`, `auditLogs`, `securityIncidents`, `sessionBans`, `customers`, `audiences`, `optOuts` | `app/api/admin/*` | | Admin: call policy and do-not-call list | `Client.callPolicy`, `callOptOuts` | `app/api/admin/call-consent/route.ts` | | Admin: webhooks, deliveries, events, session settings, platform | `Client<"project">.webhooks`, `webhookDeliveries`, `events`, `sessionConfiguration`, `SystemClient`, `BridgeClient` | `app/api/admin/*` | diff --git a/examples/full-platform/app/api/admin/organization/route.ts b/examples/full-platform/app/api/admin/organization/route.ts index 28613362..2e909e8e 100644 --- a/examples/full-platform/app/api/admin/organization/route.ts +++ b/examples/full-platform/app/api/admin/organization/route.ts @@ -1,29 +1,14 @@ import { organization } from "../../../../lib/polymorfa.js"; -import { env } from "../../../../lib/env.js"; -import { action, route, text, unknownAction } from "../../../../lib/route.js"; +import { route } from "../../../../lib/route.js"; export const GET = route("admin", async () => { const client = organization(); - const [org, members, apiKeys, projectTokens] = await Promise.all([ + const [org, members] = await Promise.all([ client.organizations.retrieve(), client.members.list(), - client.apiKeys.list(), - client.projectTokens.list(env.projectId()), ]); return { organization: org.data.data, members: members.data.data, - // Key and token metadata only; secrets are never returned. - apiKeys: apiKeys.data.data, - projectTokens: projectTokens.data.data, }; }); - -export const POST = route("admin", async ({ body }) => { - switch (action(body)) { - case "deactivateApiKey": - return organization().apiKeys.deactivate(text(body, "keyId")); - default: - return unknownAction(action(body)); - } -}); diff --git a/examples/full-platform/lib/desk/demo-fixtures.ts b/examples/full-platform/lib/desk/demo-fixtures.ts index 514a34a1..609adfca 100644 --- a/examples/full-platform/lib/desk/demo-fixtures.ts +++ b/examples/full-platform/lib/desk/demo-fixtures.ts @@ -47,41 +47,6 @@ const FIXTURES: Readonly> = { joinedAt: iso(12 * DAY), }, ], - apiKeys: [ - { - id: "key_1", - name: "Production server", - prefix: "pmfa_live_7Hq", - active: true, - lastUsedAt: iso(2 * 60 * 1000), - createdAt: iso(120 * DAY), - }, - { - id: "key_2", - name: "CI deploys", - prefix: "pmfa_live_2kL", - active: true, - lastUsedAt: iso(3 * DAY), - createdAt: iso(60 * DAY), - }, - { - id: "key_3", - name: "Old staging", - prefix: "pmfa_live_9aZ", - active: false, - lastUsedAt: iso(80 * DAY), - createdAt: iso(300 * DAY), - }, - ], - projectTokens: [ - { - id: "ptk_1", - name: "Acme Support app", - prefix: "pmfa_pt_c81", - scopes: ["messaging"], - createdAt: iso(40 * DAY), - }, - ], }, "/api/admin/projects": [ { diff --git a/examples/full-platform/ui/pages/admin.tsx b/examples/full-platform/ui/pages/admin.tsx index f76af665..b309f376 100644 --- a/examples/full-platform/ui/pages/admin.tsx +++ b/examples/full-platform/ui/pages/admin.tsx @@ -289,7 +289,7 @@ function Panel({ tab }: { readonly tab: string }) { case "organization": return (
- {(data, reload) => { + {(data) => { const org = field(data, "organization") as Row | undefined; return ( <> @@ -307,10 +307,6 @@ function Panel({ tab }: { readonly tab: string }) { label="Members" value={rowsOf(field(data, "members")).length} /> - @@ -320,44 +316,6 @@ function Panel({ tab }: { readonly tab: string }) { keys={["name", "email", "role", "joinedAt"]} /> - - - row.active !== false && ( - - ) - } - /> - - -
- ); }} diff --git a/packages/typescript/README.md b/packages/typescript/README.md index ae015e2a..ea2e3efa 100644 --- a/packages/typescript/README.md +++ b/packages/typescript/README.md @@ -2570,12 +2570,11 @@ access; without it the API returns `403 feature_unavailable`. ## Organization access and security -The organization view exposes key metadata, members, audit logs, session bans, -security incidents, and project-token metadata: +The organization view exposes members, audit logs, session bans and +security incidents: ```ts -const [keys, members, audit, bans, incidents, tokens] = await Promise.all([ - platform.apiKeys.list(), +const [members, audit, bans, incidents] = await Promise.all([ platform.members.list(), platform.auditLogs.list({ action: "session.stop", @@ -2584,44 +2583,20 @@ const [keys, members, audit, bans, incidents, tokens] = await Promise.all([ }), platform.sessionBans.listActive(), platform.securityIncidents.list(), - platform.projectTokens.list("018f0000-0000-7000-8000-000000000002"), ]); await platform.securityIncidents.acknowledge(incidents.data.data[0]!.id, { idempotencyKey: "acknowledge-incident-1", }); -await platform.apiKeys.deactivate(keys.data.data[0]!.keyId, { - idempotencyKey: "deactivate-key-1", -}); ``` -These read operations require `sessions:read`. API-key deactivation and incident -acknowledgement require `sessions:manage`. The two mutations are direct -organization-scoped writes rather than asynchronous operations. The SDK retries -them only when an idempotency key is supplied, but the pinned handlers do not -persist that header. Incident acknowledgement is repeatable; an API-key -deactivation retry after an unseen successful response can return `404` because -the key is already inactive. +Create, list and revoke organization keys and project tokens as a signed-in +person in the Console. Machine credentials cannot manage those credentials. These list responses are complete arrays. The source exposes no cursor or page token. The live audit handler accepts exact `action` and `resource` filters plus a limit bounded to 1 through 500, although those query fields are -missing from the pinned OpenAPI operation. Project-token metadata requires an -explicit project ID for organization-key calls even though OpenAPI marks the -query field optional. Neither token-list operation returns bearer secrets. - -The API-key list handler currently reports the all-scopes mask for each row -instead of the stored row-specific mask. The SDK preserves that numeric wire -field without interpreting it as proof of the caller's live authorization. -Incident acknowledgement records an empty acting-user value for API-key calls; -the subsequent incident list can therefore expose an empty `acknowledgedBy` -string rather than a dashboard user ID. - -Organization updates, member role changes, member deletion, invitations, -billing top-ups, and console usage insights require a dashboard session and -are not exposed by the server SDK. Browser client tokens are rejected by the -Management API. Project tokens are accepted only by a project-scoped `Client`; -organization-only resources are absent from that view's public type. +missing from the pinned OpenAPI operation. ## QuickLink lifecycle and settings diff --git a/packages/typescript/src/client.ts b/packages/typescript/src/client.ts index eff8aad9..321f12a4 100644 --- a/packages/typescript/src/client.ts +++ b/packages/typescript/src/client.ts @@ -16,7 +16,6 @@ import { type ProjectScopedRawClient, } from "./raw.js"; import { HttpTransport } from "./transport/http.js"; -import { ApiKeysResource } from "./platform/api-keys.js"; import { AudiencesResource } from "./platform/audiences.js"; import { AuditLogsResource } from "./platform/audit-logs.js"; import { BillingResource } from "./platform/billing.js"; @@ -40,7 +39,6 @@ import { MediaResource } from "./platform/media.js"; import { MembersResource } from "./platform/members.js"; import { OptOutsResource } from "./platform/opt-outs.js"; import { OrganizationsResource } from "./platform/organizations.js"; -import { ProjectTokensResource } from "./platform/project-tokens.js"; import { ProjectsResource } from "./platform/projects.js"; import { QuickLinkSettingsResource } from "./platform/quicklink-settings.js"; import { SecurityIncidentsResource } from "./platform/security-incidents.js"; @@ -80,7 +78,6 @@ export interface ClientBase { } export interface OrganizationControlPlaneResources { - readonly apiKeys: ApiKeysResource; readonly audiences: AudiencesResource; readonly auditLogs: AuditLogsResource; readonly billing: BillingResource; @@ -96,7 +93,6 @@ export interface OrganizationControlPlaneResources { readonly media: MediaResource; readonly optOuts: OptOutsResource; readonly projects: ProjectsResource; - readonly projectTokens: ProjectTokensResource; readonly securityIncidents: SecurityIncidentsResource; readonly sessionBans: SessionBansResource; readonly sessions: PlatformSessionsResource; @@ -213,7 +209,6 @@ class ClientImplementation implements ClientBase { if (this.owner === "organization") { Object.assign(this, { - apiKeys: new ApiKeysResource(this.#transport), audiences: new AudiencesResource(this.#transport), auditLogs: new AuditLogsResource(this.#transport), billing: new BillingResource(this.#transport), @@ -227,7 +222,6 @@ class ClientImplementation implements ClientBase { media: new MediaResource(this.#transport), optOuts: new OptOutsResource(this.#transport), projects: new ProjectsResource(this.#transport), - projectTokens: new ProjectTokensResource(this.#transport), securityIncidents: new SecurityIncidentsResource(this.#transport), sessionBans: new SessionBansResource(this.#transport), sessions: new PlatformSessionsResource(this.#transport), diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index b7b90dde..e12fa7e0 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -677,7 +677,6 @@ export { WebhooksResource as MessagingWebhooksResource } from "./messaging/webho export { CursorPage, type PageDecoder, type PageResult } from "./pagination.js"; export { FollowableIndexedEventPage } from "./platform/indexed-event-page.js"; export { AudiencesResource } from "./platform/audiences.js"; -export { ApiKeysResource } from "./platform/api-keys.js"; export { AuditLogsResource } from "./platform/audit-logs.js"; export { BillingResource } from "./platform/billing.js"; export { UsageResource } from "./platform/usage.js"; @@ -742,7 +741,6 @@ export { type UpdateQuickLinkSettingsInput, } from "./platform/quicklink-settings.js"; export { OrganizationsResource } from "./platform/organizations.js"; -export { ProjectTokensResource } from "./platform/project-tokens.js"; export { ProjectsResource } from "./platform/projects.js"; export { SecurityIncidentsResource } from "./platform/security-incidents.js"; export { SessionBansResource } from "./platform/session-bans.js"; @@ -866,8 +864,6 @@ export { type WaitForVoiceAudioOptions, } from "./platform/voice.js"; export type { - ApiKey, - ApiKeyDeactivation, AuditLog, BillingBalance, BillingCurrency, @@ -951,7 +947,6 @@ export type { ProjectDefaultTier, ProjectIcon, ProjectIconInput, - ProjectToken, ProjectWithStats, SecurityIncident, SecurityIncidentAcknowledgement, diff --git a/packages/typescript/src/platform/api-keys.ts b/packages/typescript/src/platform/api-keys.ts deleted file mode 100644 index b419d32f..00000000 --- a/packages/typescript/src/platform/api-keys.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { HttpTransport } from "../transport/http.js"; -import type { ApiResponse, RequestOptions } from "../transport/types.js"; -import type { ApiKey, ApiKeyDeactivation, DataEnvelope } from "./types.js"; - -export class ApiKeysResource { - constructor(private readonly transport: HttpTransport) {} - - list( - options: RequestOptions = {}, - ): Promise>> { - return this.transport.request({ - method: "GET", - path: "/platform/keys", - ...options, - }); - } - - deactivate( - keyId: string, - options: RequestOptions = {}, - ): Promise>> { - return this.transport.request({ - method: "DELETE", - path: `/platform/keys/${encodeURIComponent(keyId)}`, - ...options, - }); - } -} diff --git a/packages/typescript/src/platform/project-tokens.ts b/packages/typescript/src/platform/project-tokens.ts deleted file mode 100644 index 9272a728..00000000 --- a/packages/typescript/src/platform/project-tokens.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HttpTransport } from "../transport/http.js"; -import type { ApiResponse, RequestOptions } from "../transport/types.js"; -import type { DataEnvelope, ProjectToken } from "./types.js"; - -export class ProjectTokensResource { - constructor(private readonly transport: HttpTransport) {} - - list( - projectId: string, - options: RequestOptions = {}, - ): Promise>> { - return this.transport.request({ - method: "GET", - path: "/platform/tokens", - query: { projectId }, - ...options, - }); - } -} diff --git a/packages/typescript/src/platform/types.ts b/packages/typescript/src/platform/types.ts index e71e1cc8..f76ed9d5 100644 --- a/packages/typescript/src/platform/types.ts +++ b/packages/typescript/src/platform/types.ts @@ -583,28 +583,6 @@ export interface TierPricing { readonly features: readonly string[]; } -/** Metadata for an organization server API key. Raw key material is never returned. */ -export interface ApiKey { - readonly _id: string; - readonly _creationTime: number; - readonly id: string; - readonly keyId: string; - readonly start: string; - readonly last4: string; - readonly orgId: string; - readonly label: string; - readonly scopes: number; - readonly source: string; - readonly expiresAt: number; - readonly lastUsed?: number; - readonly isActive: boolean; -} - -export interface ApiKeyDeactivation { - readonly ok: true; - readonly keyId: string; -} - export type OrganizationMemberRole = "owner" | "admin" | "member"; export interface OrganizationMember { @@ -676,19 +654,6 @@ export interface SecurityIncidentAcknowledgement { readonly acknowledged: true; } -/** Project-token metadata. The bearer token itself is never returned. */ -export interface ProjectToken { - readonly id: string; - readonly start: string; - readonly last4: string; - readonly label: string | null; - readonly scopes: number; - readonly expiresAt: number | null; - readonly createdAt: number; - readonly lastUsedAt: number | null; - readonly revokedAt: number | null; -} - export interface ProjectIcon { readonly type: string; readonly value: string; diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index 29b5016d..d8df8886 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -211,7 +211,7 @@ describe("reconciled coverage evidence", () => { } }); - it("pins Analytics to the same full Platform source and snapshot hash", () => { + it("pins Analytics to its original source and the credential-removal snapshot", () => { const source = JSON.parse( readFileSync(repositoryFile("contracts/source.json"), "utf8"), ) as { @@ -219,9 +219,17 @@ describe("reconciled coverage evidence", () => { contracts: { platform: { sha256: string } }; supplements?: unknown; }; - expect(source.supplements).toBeUndefined(); + expect(source.supplements).toEqual([ + { + name: "human-only-credential-management", + snapshotPath: "contracts/credential-management.json", + sourceCommit: "cdc3d8dd2770693f1d319ecf5d0fa1a058717a0d", + sha256: + "bda73eec25574a37c9fa412d5052d0c163322d6b3df0fa7daafcdaa7132e3223", + }, + ]); expect(source.contracts.platform.sha256).toBe( - "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", + "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5", ); for (const id of [ "getWhatsAppAnalytics", @@ -235,7 +243,7 @@ describe("reconciled coverage evidence", () => { }; expect(operation.sourceCommit).toBe(source.commit); expect(operation.sourceContractSha256).toBe( - source.contracts.platform.sha256, + "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", ); expect(operation.sdkContractSha256).toBe( source.contracts.platform.sha256, @@ -243,6 +251,30 @@ describe("reconciled coverage evidence", () => { } }); + it("pins the removal supplement and excludes its operations from snapshots and SDK coverage", () => { + const supplement = JSON.parse( + readFileSync( + repositoryFile("contracts/credential-management.json"), + "utf8", + ), + ); + const platform = JSON.parse( + readFileSync(repositoryFile("contracts/openapi.platform.json"), "utf8"), + ); + expect(supplement.sourceCommit).toBe( + "cdc3d8dd2770693f1d319ecf5d0fa1a058717a0d", + ); + expect(supplement.removedOperations).toHaveLength(3); + for (const removed of supplement.removedOperations) { + expect(platform.paths).not.toHaveProperty(removed.path); + expect( + ledger.operations.some( + (op) => op.path === removed.path && op.method === removed.method, + ), + ).toBe(false); + } + }); + it("maps all four hosted-history reads to the server SDK", () => { expect(entry("listChats").typescript).toEqual({ status: "covered", diff --git a/packages/typescript/test/coverage.test.ts b/packages/typescript/test/coverage.test.ts index 78a25d71..9a89bb41 100644 --- a/packages/typescript/test/coverage.test.ts +++ b/packages/typescript/test/coverage.test.ts @@ -174,8 +174,8 @@ describe("coverage checker", () => { expect(result.status, result.stderr).toBe(0); expect(result.report).toMatchObject({ sourceCommit: "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb", - total: 608, - covered: 454, + total: 605, + covered: 451, partial: 0, missing: 14, excluded: 140, @@ -653,8 +653,6 @@ describe("coverage checker", () => { }>; }; const operationIds = [ - "deactivateApiKey", - "listApiKeys", "listMembers", "listAuditLogs", "listSessionBans", @@ -662,7 +660,6 @@ describe("coverage checker", () => { "listSecurityIncidents", "acknowledgeSecurityIncident", "getPlatformOperation", - "listPolymorfaTokens", "inviteMember", "updateMemberRole", "deleteMember", @@ -674,14 +671,6 @@ describe("coverage checker", () => { ); expect(mappings).toMatchObject({ - deactivateApiKey: { - status: "covered", - method: "Client.apiKeys.deactivate", - }, - listApiKeys: { - status: "covered", - method: "Client.apiKeys.list", - }, listMembers: { status: "covered", method: "Client.members.list", @@ -710,10 +699,6 @@ describe("coverage checker", () => { status: "covered", method: "Client.operations.get", }, - listPolymorfaTokens: { - status: "covered", - method: "Client.projectTokens.list", - }, inviteMember: { status: "excluded" }, updateMemberRole: { status: "excluded" }, deleteMember: { status: "excluded" }, diff --git a/packages/typescript/test/exports.test.ts b/packages/typescript/test/exports.test.ts index 84536de4..985b59b1 100644 --- a/packages/typescript/test/exports.test.ts +++ b/packages/typescript/test/exports.test.ts @@ -1,7 +1,6 @@ import { describe, expect, expectTypeOf, it } from "vitest"; import { - ApiKeysResource, AuditLogsResource, AudiencesResource, BillingResource, @@ -29,7 +28,6 @@ import { QuickRepliesResource, QuickLinkSettingsResource, Client, - ProjectTokensResource, PRIVACY_SETTING_VALUES, PRESENCE_CHAT_STATES, PRESENCE_OBSERVATION_STATUSES, @@ -49,7 +47,6 @@ import { constructWebhookEvent, isEvent, verifyWebhookSignature, - type ApiKey, type ApiResponse, type AuditLog, type BillingBalance, @@ -96,7 +93,6 @@ import { type OrganizationMember, type PlatformPayload, type ClientOptions, - type ProjectToken, type RawRequest, type RequestOptions, type ResolveIdentityParams, @@ -138,7 +134,6 @@ describe("public exports", () => { MessagingCampaignsResource, MessagingMediaResource, Client, - ApiKeysResource, AuditLogsResource, AudiencesResource, BillingResource, @@ -160,7 +155,6 @@ describe("public exports", () => { PrivacyResource, PresenceResource, QuickRepliesResource, - ProjectTokensResource, CursorPage, PolymorfaError, PolymorfaConfigurationError, @@ -177,12 +171,11 @@ describe("public exports", () => { constructWebhookEvent, verifyWebhookSignature, isEvent, - ]).toHaveLength(43); + ]).toHaveLength(41); }); it("exposes every public CLI-facing type from one entrypoint", () => { expectTypeOf>().toHaveProperty("metadata"); - expectTypeOf().toHaveProperty("last4"); expectTypeOf().toHaveProperty("actorEmail"); expectTypeOf().toHaveProperty("balanceCents"); expectTypeOf().toHaveProperty("activeNumbers"); @@ -217,7 +210,6 @@ describe("public exports", () => { expectTypeOf().toMatchTypeOf< Readonly> >(); - expectTypeOf().toHaveProperty("revokedAt"); expectTypeOf().toHaveProperty("resolution"); expectTypeOf().toHaveProperty("status"); expectTypeOf().toHaveProperty("sessionIds"); diff --git a/packages/typescript/test/package.test.ts b/packages/typescript/test/package.test.ts index 0612181a..2d5bce52 100644 --- a/packages/typescript/test/package.test.ts +++ b/packages/typescript/test/package.test.ts @@ -73,7 +73,7 @@ describe("npm package", () => { 'const bridge = new BridgeClient({ credential: { type: "projectToken", value: "pmfa_pt_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" } });', "let listenerCredentialRejected = false;", 'try { new Client({ credential: { type: "organizationApiKey", value: "pmfa_ls_fixture" } }); } catch { listenerCredentialRejected = true; }', - 'console.log(JSON.stringify({ version: SDK_VERSION, messaging: !!messaging.raw, business: typeof messaging.business.getCatalog, calls: typeof messaging.calls.reject, campaigns: typeof messaging.campaigns.launch, messagingMedia: typeof messaging.media.download, chats: typeof messaging.chats.editMessage, channels: typeof messaging.channels.listMessageUpdates, contacts: typeof messaging.contacts.list, groups: typeof messaging.groups.list, labels: typeof messaging.labels.list, lids: typeof messaging.identities.resolve, observationPolicies: typeof messaging.observationPolicies.retrieveForProject, profile: typeof messaging.profile.get, privacy: typeof messaging.privacy.set, privacyValues: PRIVACY_SETTING_VALUES.defense, presence: typeof messaging.presence.getForChat, presenceStates: PRESENCE_STATES, quickReplies: typeof messaging.quickReplies.list, pairing: typeof messaging.sessions.requestPairingCode, messagingOperations: typeof messaging.operations, templates: typeof messaging.templates.create, users: typeof messaging.users.getSecurityCode, systemStatus: typeof system.status, systemVersion: typeof system.version, systemHealth: typeof system.health, systemPing: typeof system.ping, bridgeRoutes: typeof bridge.routes.resolve, bridgeListen: typeof bridge.listen, platform: !!platform.raw, platformOwner: platform.owner, projectOwner: project.owner, projectId: project.projectId, apiKeys: typeof platform.apiKeys.deactivate, auditLogs: typeof platform.auditLogs.list, billing: typeof platform.billing.usage, members: typeof platform.members.list, events: typeof project.events.replay, webhooks: typeof project.webhooks.rotateSecret, webhookDeliveries: typeof project.webhookDeliveries.retrieveAttempt, operations: typeof project.operations.wait, projectTokens: typeof platform.projectTokens.list, securityIncidents: typeof platform.securityIncidents.acknowledge, sessionBans: typeof platform.sessionBans.listActive, sessionStart: typeof platform.sessions.start, batchStop: typeof platform.sessions.stopMany, quickLinkSettings: typeof project.quickLinkSettings.update, verifyWebhook: typeof webhooks.verify, createWebhookFixture: typeof webhooks.createFixture, listenerCredentialRejected, platformClientExported: "PlatformClient" in sdk, listenerApiExported: "eventStreams" in sdk, memberInvite: typeof platform.members.invite, organizationUpdate: typeof platform.organizations.update }));', + 'console.log(JSON.stringify({ version: SDK_VERSION, messaging: !!messaging.raw, business: typeof messaging.business.getCatalog, calls: typeof messaging.calls.reject, campaigns: typeof messaging.campaigns.launch, messagingMedia: typeof messaging.media.download, chats: typeof messaging.chats.editMessage, channels: typeof messaging.channels.listMessageUpdates, contacts: typeof messaging.contacts.list, groups: typeof messaging.groups.list, labels: typeof messaging.labels.list, lids: typeof messaging.identities.resolve, observationPolicies: typeof messaging.observationPolicies.retrieveForProject, profile: typeof messaging.profile.get, privacy: typeof messaging.privacy.set, privacyValues: PRIVACY_SETTING_VALUES.defense, presence: typeof messaging.presence.getForChat, presenceStates: PRESENCE_STATES, quickReplies: typeof messaging.quickReplies.list, pairing: typeof messaging.sessions.requestPairingCode, messagingOperations: typeof messaging.operations, templates: typeof messaging.templates.create, users: typeof messaging.users.getSecurityCode, systemStatus: typeof system.status, systemVersion: typeof system.version, systemHealth: typeof system.health, systemPing: typeof system.ping, bridgeRoutes: typeof bridge.routes.resolve, bridgeListen: typeof bridge.listen, platform: !!platform.raw, platformOwner: platform.owner, projectOwner: project.owner, projectId: project.projectId, apiKeys: typeof platform.apiKeys, auditLogs: typeof platform.auditLogs.list, billing: typeof platform.billing.usage, members: typeof platform.members.list, events: typeof project.events.replay, webhooks: typeof project.webhooks.rotateSecret, webhookDeliveries: typeof project.webhookDeliveries.retrieveAttempt, operations: typeof project.operations.wait, projectTokens: typeof platform.projectTokens, securityIncidents: typeof platform.securityIncidents.acknowledge, sessionBans: typeof platform.sessionBans.listActive, sessionStart: typeof platform.sessions.start, batchStop: typeof platform.sessions.stopMany, quickLinkSettings: typeof project.quickLinkSettings.update, verifyWebhook: typeof webhooks.verify, createWebhookFixture: typeof webhooks.createFixture, listenerCredentialRejected, platformClientExported: "PlatformClient" in sdk, listenerApiExported: "eventStreams" in sdk, memberInvite: typeof platform.members.invite, organizationUpdate: typeof platform.organizations.update }));', ].join("\n"), ); const imported = spawnSync(process.execPath, [consumer], { @@ -115,7 +115,7 @@ describe("npm package", () => { platformOwner: "organization", projectOwner: "project", projectId: "project_123", - apiKeys: "function", + apiKeys: "undefined", auditLogs: "function", billing: "function", members: "function", @@ -123,7 +123,7 @@ describe("npm package", () => { webhooks: "function", webhookDeliveries: "function", operations: "function", - projectTokens: "function", + projectTokens: "undefined", securityIncidents: "function", sessionBans: "function", sessionStart: "function", diff --git a/packages/typescript/test/platform-access.test.ts b/packages/typescript/test/platform-access.test.ts index 34b8c5f3..b8772030 100644 --- a/packages/typescript/test/platform-access.test.ts +++ b/packages/typescript/test/platform-access.test.ts @@ -1,21 +1,18 @@ +import * as publicApi from "../src/index.js"; import { ORGANIZATION_API_KEY } from "./support/credentials.js"; import { afterEach, describe, expect, expectTypeOf, it } from "vitest"; import { - ApiKeysResource, AuditLogsResource, MembersResource, Client, - ProjectTokensResource, SecurityIncidentsResource, SessionBansResource, type ApiResponse, - type ApiKey, type AuditLog, type DataEnvelope, type ManagementOperation, type OrganizationMember, - type ProjectToken, type SecurityIncident, type SessionBan, } from "../src/index.js"; @@ -58,7 +55,6 @@ async function platformAccessServer(): Promise<{ describe("Client organization access and operations", () => { it("exports the complete resource and response contracts", () => { - expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); @@ -66,39 +62,21 @@ describe("Client organization access and operations", () => { Client["securityIncidents"] >().toEqualTypeOf(); expectTypeOf().toHaveProperty("operations"); - expectTypeOf< - Client["projectTokens"] - >().toEqualTypeOf(); - expectTypeOf().toHaveProperty("lastUsed"); expectTypeOf().toHaveProperty("joinedAt"); expectTypeOf().toHaveProperty("metadata"); expectTypeOf().toHaveProperty("banExpiresAt"); expectTypeOf().toHaveProperty("acknowledgedAt"); expectTypeOf().toHaveProperty("capabilities"); - expectTypeOf().toHaveProperty("revokedAt"); }); - it("lists and deactivates organization API keys without exposing key material", async () => { + it("has no organization-key or project-token management resources", async () => { const { client, requests } = await platformAccessServer(); - const listed = await client.apiKeys.list(); - const deactivated = await client.apiKeys.deactivate("key/a", { - idempotencyKey: "deactivate-key-a", - }); - - expectTypeOf(listed).toEqualTypeOf< - ApiResponse> - >(); - expectTypeOf(deactivated.data.data).toEqualTypeOf<{ - readonly ok: true; - readonly keyId: string; - }>(); - expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ - "GET /platform/keys", - "DELETE /platform/keys/key%2Fa", - ]); - expect(requests[1]?.headers["idempotency-key"]).toBe("deactivate-key-a"); - expect(listed.metadata.requestId).toBe("req_platform_access"); + expect(publicApi).not.toHaveProperty("ApiKeysResource"); + expect(publicApi).not.toHaveProperty("ProjectTokensResource"); + expect(client).not.toHaveProperty("apiKeys"); + expect(client).not.toHaveProperty("projectTokens"); + expect(requests).toHaveLength(0); }); it("lists members but excludes dashboard-only invitations and mutations", async () => { @@ -176,18 +154,4 @@ describe("Client organization access and operations", () => { ]); expect(requests[1]?.headers["idempotency-key"]).toBe("ack-incident-a"); }); - - it("retrieves durable operation state and requires a project for token metadata", async () => { - const { client, requests } = await platformAccessServer(); - const tokens = await client.projectTokens.list("project/a", { - timeoutMs: 5_000, - }); - - expectTypeOf(tokens).toEqualTypeOf< - ApiResponse> - >(); - expect(requests.map(({ method, path }) => `${method} ${path}`)).toEqual([ - "GET /platform/tokens?projectId=project%2Fa", - ]); - }); }); From 0b1dbf415fdf03e89c307fb169143a91df96f5f0 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 17 Oct 2026 21:14:31 +0300 Subject: [PATCH 2/2] test: preserve contract formatting and pin credential-removal snapshot --- contracts/coverage.json | 8 +- contracts/openapi.platform.json | 112 +++++++++--------- contracts/source.json | 2 +- .../test/coverage-reconciliation.test.ts | 2 +- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/contracts/coverage.json b/contracts/coverage.json index ed178eab..40b2740c 100644 --- a/contracts/coverage.json +++ b/contracts/coverage.json @@ -3341,7 +3341,7 @@ }, "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb", - "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5" + "sdkContractSha256": "dc3c6ddde8280f9d508d725af6c9413aeb089445cdd6677f9774ec994ca6e926" }, { "family": "platform", @@ -3354,7 +3354,7 @@ }, "fingerprint": "e999dce00d84dd7b4d40958ea76527c37c27fc867fa7ccf5f9f4e713c1f12555", "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", - "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5", + "sdkContractSha256": "dc3c6ddde8280f9d508d725af6c9413aeb089445cdd6677f9774ec994ca6e926", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb" }, { @@ -4256,7 +4256,7 @@ }, "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb", - "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5" + "sdkContractSha256": "dc3c6ddde8280f9d508d725af6c9413aeb089445cdd6677f9774ec994ca6e926" }, { "family": "platform", @@ -4269,7 +4269,7 @@ }, "fingerprint": "240ac51bf9ba12d47624b0cd8ba96b018d1b2e0e7e7e4e8e4d8c3152025c4fab", "sourceContractSha256": "97fd5ca378699350c53412267a39ef66cc64111283c7bfd155fa932d77bf925b", - "sdkContractSha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5", + "sdkContractSha256": "dc3c6ddde8280f9d508d725af6c9413aeb089445cdd6677f9774ec994ca6e926", "sourceCommit": "e3d1d2dbc8287f79daf0a1d6f6f5375c1a9d9dbb" }, { diff --git a/contracts/openapi.platform.json b/contracts/openapi.platform.json index 35254d14..81cfc464 100644 --- a/contracts/openapi.platform.json +++ b/contracts/openapi.platform.json @@ -3,7 +3,7 @@ "info": { "title": "Polymorfa Console & Management API", "version": "1.0.0", - "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/\u2026` \u2014 **public registry** (267 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/\u2026` \u2014 **console-only** (114 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." + "description": "Complete console/management surface. Resource-oriented REST: `{param}` path variables, single-word action sub-resources.\n\nNamespaces decide inclusion:\n- `/platform/…` — **public registry** (267 endpoints): each endpoint declares either a scoped `pmfa_` organization key or a verified live dashboard JWT, or dashboard-only authorization when a specific user's authority is required. Only implemented handlers are published to customers and SDKs.\n- `/console/…` — **console-only** (114 endpoints): verified dashboard identity. EXCLUDED from the published spec; reason per endpoint.\n\nThe published customer spec = this document filtered to `x-audience: public` endpoints with an implemented runtime handler.\nMost transitional body/response schemas remain loose (`additionalProperties: true`); session-control operations use explicit strict schemas." }, "servers": [ { @@ -960,7 +960,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -1123,7 +1123,7 @@ "x-mint": { "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" }, - "description": "**Required scopes (all):** `sessions:manage` \u00b7 `messages:write` \u00b7 `contacts:read` \u00b7 `presence:read` \u00b7 `presence:observe` \u00b7 `mcp`", + "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`", "x-audience": "public" } }, @@ -1173,7 +1173,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -1401,7 +1401,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -1564,7 +1564,7 @@ "x-mint": { "content": "sessions:manage messages:write contacts:read presence:read presence:observe mcp" }, - "description": "**Required scopes (all):** `sessions:manage` \u00b7 `messages:write` \u00b7 `contacts:read` \u00b7 `presence:read` \u00b7 `presence:observe` \u00b7 `mcp`", + "description": "**Required scopes (all):** `sessions:manage` · `messages:write` · `contacts:read` · `presence:read` · `presence:observe` · `mcp`", "x-audience": "public" }, "delete": { @@ -1612,7 +1612,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -1827,7 +1827,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -2051,7 +2051,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -2286,7 +2286,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -2497,7 +2497,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -2875,7 +2875,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -3149,7 +3149,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -3455,7 +3455,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -3690,7 +3690,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -3905,7 +3905,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -4116,7 +4116,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -4393,7 +4393,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -4616,7 +4616,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -4892,7 +4892,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -5138,7 +5138,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -5405,7 +5405,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -5616,7 +5616,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -5895,7 +5895,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -6127,7 +6127,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -6372,7 +6372,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -6618,7 +6618,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -6862,7 +6862,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -7073,7 +7073,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -7382,7 +7382,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -7724,7 +7724,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -8001,7 +8001,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -8224,7 +8224,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -8468,7 +8468,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -8714,7 +8714,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -8993,7 +8993,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -9204,7 +9204,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -9545,7 +9545,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -9758,7 +9758,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -9972,7 +9972,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -10216,7 +10216,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -10436,7 +10436,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -10882,7 +10882,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -11095,7 +11095,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -11308,7 +11308,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -11481,7 +11481,7 @@ "tags": [ "Sessions" ], - "summary": "Initiate M\u2194P toggle: fetch the destination Curve25519 pubkey", + "summary": "Initiate M↔P toggle: fetch the destination Curve25519 pubkey", "security": [ { "ApiKeyAuth": [] @@ -11521,7 +11521,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -11743,7 +11743,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -11965,7 +11965,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -12138,7 +12138,7 @@ "tags": [ "Sessions" ], - "summary": "Acknowledge receipt of an M\u2192P export; API flips bartenderMode after hash verify", + "summary": "Acknowledge receipt of an M→P export; API flips bartenderMode after hash verify", "security": [ { "ApiKeyAuth": [] @@ -12166,7 +12166,7 @@ }, "responses": { "200": { - "description": "Ack accepted; bartenderMode flipped from magic \u2192 passthrough", + "description": "Ack accepted; bartenderMode flipped from magic → passthrough", "content": { "application/json": { "schema": { @@ -12187,7 +12187,7 @@ } }, "400": { - "description": "Bad request \u2014 request body or params failed validation", + "description": "Bad request — request body or params failed validation", "content": { "application/json": { "schema": { @@ -38320,11 +38320,11 @@ "keys" ], "summary": "Mint for console api key", - "description": "**EXCLUDED from the public API** (`console`). Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only \u2014 keeps revocation meaningful.", + "description": "**EXCLUDED from the public API** (`console`). Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", "x-audience": "console", "x-polymorfa-resource-boundary": "human_only", "x-excluded-from-public": true, - "x-exclusion-reason": "Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only \u2014 keeps revocation meaningful.", + "x-exclusion-reason": "Credential red line: a pmfa_ key must never mint a pmfa_ key. Human session only — keeps revocation meaningful.", "security": [ { "ConsoleSession": [] @@ -53758,7 +53758,7 @@ "type": "string", "maxLength": 80, "nullable": true, - "description": "Page title shown above the pairing flow. Defaults to \u201cConnect WhatsApp\u201d." + "description": "Page title shown above the pairing flow. Defaults to “Connect WhatsApp”." }, "description": { "type": "string", @@ -67523,7 +67523,7 @@ "enum": [ "passthrough_plus" ], - "description": "When set, atomically flips sessions.config.bartenderMode \u2192 passthrough_plus after the keys persist. Eliminates the window where mode is set but no keys are staged." + "description": "When set, atomically flips sessions.config.bartenderMode → passthrough_plus after the keys persist. Eliminates the window where mode is set but no keys are staged." } }, "required": [ diff --git a/contracts/source.json b/contracts/source.json index a6d95d93..169eddcc 100644 --- a/contracts/source.json +++ b/contracts/source.json @@ -13,7 +13,7 @@ "platform": { "sourcePath": "apps/api/docs/openapi.management.json", "snapshotPath": "contracts/openapi.platform.json", - "sha256": "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5" + "sha256": "dc3c6ddde8280f9d508d725af6c9413aeb089445cdd6677f9774ec994ca6e926" } }, "supplements": [ diff --git a/packages/typescript/test/coverage-reconciliation.test.ts b/packages/typescript/test/coverage-reconciliation.test.ts index d8df8886..cc63d799 100644 --- a/packages/typescript/test/coverage-reconciliation.test.ts +++ b/packages/typescript/test/coverage-reconciliation.test.ts @@ -229,7 +229,7 @@ describe("reconciled coverage evidence", () => { }, ]); expect(source.contracts.platform.sha256).toBe( - "ba693410c483b444cf28b673bf6da90bb643cc319b38a611ab0e15988dd015e5", + "dc3c6ddde8280f9d508d725af6c9413aeb089445cdd6677f9774ec994ca6e926", ); for (const id of [ "getWhatsAppAnalytics",