From 41c3e12dc83d9759181d94c748544c80d33917e3 Mon Sep 17 00:00:00 2001 From: Gorka Date: Thu, 28 May 2026 14:59:22 -0300 Subject: [PATCH 1/4] fix(test): write E2E_PP_PUBLIC_KEY in the local e2e setup The CI flow's `e2e/setup.sh` was updated to include the URL-scoped PP key, but the local `./test.sh e2e` runner uses a separate `test/setup-e2e.ts` writer that produces /config/contracts.env from the setup container. Without E2E_PP_PUBLIC_KEY in that file, the test-runner's loadConfig() throws before any bundle is submitted. --- test/setup-e2e.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/setup-e2e.ts b/test/setup-e2e.ts index 7d6cfd3..85be850 100644 --- a/test/setup-e2e.ts +++ b/test/setup-e2e.ts @@ -244,6 +244,9 @@ E2E_CHANNEL_ASSET_CONTRACT_ID=${assetContractId} # Provider keypair (registered on-chain by setup) E2E_PROVIDER_PK=${provider.publicKey()} E2E_PROVIDER_SK=${provider.secret()} +# Bundles are URL-scoped to /providers/:ppPublicKey/bundles; in the single-PP +# e2e harness the PP key == the provider key. +E2E_PP_PUBLIC_KEY=${provider.publicKey()} # Admin/Council keypair (for governance tests) E2E_ADMIN_SK=${admin.secret()} From 2f84373023e3d4a9086a4df74a84d5f59e15b0e4 Mon Sep 17 00:00:00 2001 From: Gorka Date: Thu, 28 May 2026 17:02:23 -0300 Subject: [PATCH 2/4] fix(lifecycle): wire ppPublicKey + entity registration in CI runner lifecycle/ci-test.ts is the test runner invoked by lifecycle's docker compose (and by provider-platform's lifecycle-reusable CI). Two fields were missed when lifecycle/main.ts was updated for the URL-scoped bundles work: - e2eConfig was missing ppPublicKey, so lib/client/bundle.ts composed /providers/undefined/bundles, which the resolver couldn't service. - Alice and Bob weren't registered as APPROVED entities before deposit/send, tripping the SUBMITTER_NOT_APPROVED gate. Mirrors the equivalent fixes already in lifecycle/main.ts. --- lifecycle/ci-test.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lifecycle/ci-test.ts b/lifecycle/ci-test.ts index 8e5adfd..9c027a5 100644 --- a/lifecycle/ci-test.ts +++ b/lifecycle/ci-test.ts @@ -60,6 +60,25 @@ function loadEnvFile(path: string): Record { return env; } +async function registerEntity( + providerUrl: string, + pubkey: string, + name: string, +): Promise { + const res = await fetch(`${providerUrl}/api/v1/entities`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ pubkey, name, jurisdictions: [] }), + }); + // 409 = already APPROVED; treat as success for idempotency. + if (!res.ok && res.status !== 409) { + throw new Error( + `Entity registration failed for ${pubkey}: ${res.status} ${await res + .text()}`, + ); + } +} + async function fundAccount(publicKey: string): Promise { const res = await fetch(`${FRIENDBOT_URL}?addr=${publicKey}`); if (!res.ok) { @@ -198,6 +217,9 @@ async function main() { horizonUrl, friendbotUrl: FRIENDBOT_URL, providerUrl: PROVIDER_URL, + // Bundles are URL-scoped to /providers/:ppPublicKey/bundles. Lifecycle + // CI runs against the single seeded PP, so ppPublicKey == ppOperator. + ppPublicKey: ppOperator.publicKey(), channelContractId: channelContractId as ContractId, channelAuthId: channelAuthId as ContractId, channelAssetContractId: assetContractId as ContractId, @@ -418,11 +440,15 @@ async function main() { const aliceJwt = await authenticate(alice, e2eConfig); console.log(" Alice authenticated"); + await registerEntity(PROVIDER_URL, alice.publicKey(), "Alice"); + console.log(" Alice approved as entity"); await deposit(alice.secret(), DEPOSIT_AMOUNT, aliceJwt, e2eConfig); console.log(` Deposit ${DEPOSIT_AMOUNT} XLM complete`); const bobJwt = await authenticate(bob, e2eConfig); console.log(" Bob authenticated"); + await registerEntity(PROVIDER_URL, bob.publicKey(), "Bob"); + console.log(" Bob approved as entity"); const receiverOps = await prepareReceive( bob.secret(), SEND_AMOUNT, From 3963530132865b7bba0e4608b27a01cb41b95d84 Mon Sep 17 00:00:00 2001 From: Gorka Date: Thu, 28 May 2026 17:02:57 -0300 Subject: [PATCH 3/4] fix(governance): read councilMemberships (plural) on PP list response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit provider-platform PR #103 (events dashboard redesign) renamed councilMembership (singular) → councilMemberships (plural array) on the dashboard /pp/list response. The governance e2e test was never updated to match. Now reads [0]?.status for the approved/rejected checks and .length === 0 for the no-council case. --- e2e/governance/uc2-approve-reject.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/e2e/governance/uc2-approve-reject.ts b/e2e/governance/uc2-approve-reject.ts index 49a3e06..d190785 100644 --- a/e2e/governance/uc2-approve-reject.ts +++ b/e2e/governance/uc2-approve-reject.ts @@ -377,9 +377,12 @@ check("3 PPs", list.data.data?.length, 3); const la = list.data.data?.find((p: any) => p.label === "PP-Approve"); const lr = list.data.data?.find((p: any) => p.label === "PP-Reject"); const ln = list.data.data?.find((p: any) => p.label === "PP-None"); -check("PP-Approve: ACTIVE", la?.councilMembership?.status, "ACTIVE"); -check("PP-Reject: REJECTED", lr?.councilMembership?.status, "REJECTED"); -check("PP-None: no council", ln?.councilMembership, null); +// provider-platform PR #103 renamed councilMembership (singular) → +// councilMemberships (plural array). A PP can be a member of multiple +// councils; this test creates exactly one membership per PP. +check("PP-Approve: ACTIVE", la?.councilMemberships?.[0]?.status, "ACTIVE"); +check("PP-Reject: REJECTED", lr?.councilMemberships?.[0]?.status, "REJECTED"); +check("PP-None: no council", ln?.councilMemberships?.length, 0); // --- Test 5: Council shows both --- console.log("\n\x1b[34m=== Test 5: Council requests list ===\x1b[0m"); From ab39b8001676147c89334550372b37b1ad0a1ca8 Mon Sep 17 00:00:00 2001 From: Gorka Date: Thu, 28 May 2026 17:05:08 -0300 Subject: [PATCH 4/4] fix(pos-instant): register pay-service as APPROVED entity Pay-platform submits bundles to provider-platform under its PAY_SERVICE_SK identity. provider-platform now gates bundle admission on the submitter being an APPROVED entity (SUBMITTER_NOT_APPROVED), so the pay-service keypair needs an entity record before the instant payment flow can run. New [1b/5] step calls POST /api/v1/entities with keys.payService right after funding. --- e2e/pos-instant/main.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/e2e/pos-instant/main.ts b/e2e/pos-instant/main.ts index 5c3525d..baf7354 100644 --- a/e2e/pos-instant/main.ts +++ b/e2e/pos-instant/main.ts @@ -82,6 +82,28 @@ await Promise.all([ ]); console.log(` Funded (${elapsed()})`); +// [1b] Register pay-service as an APPROVED entity on provider-platform. +// Pay-platform submits bundles to provider-platform under its PAY_SERVICE_SK +// identity; provider-platform now gates bundle admission on the submitter +// being an APPROVED entity, so this registration is required. +console.log("\n[1b/5] Registering pay-service as APPROVED entity..."); +const entityRes = await fetch(`${PROVIDER_URL}/api/v1/entities`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + pubkey: keys.payService.publicKey(), + name: "Pay Service", + jurisdictions: [], + }), +}); +// 409 = already APPROVED; treat as success for idempotency. +if (!entityRes.ok && entityRes.status !== 409) { + throw new Error( + `Entity registration failed: ${entityRes.status} ${await entityRes.text()}`, + ); +} +console.log(` Pay-service approved (${elapsed()})`); + // [2] Create merchant on pay-platform + store UTXOs console.log("\n[2/5] Creating merchant account + UTXOs..."); const merchantJwt = await getPayJwt(