Skip to content

URL-scope bundle submission to /providers/:pp/entity/bundles#36

Merged
AquiGorka merged 3 commits into
mainfrom
feat/pp-url-scoping
Jun 1, 2026
Merged

URL-scope bundle submission to /providers/:pp/entity/bundles#36
AquiGorka merged 3 commits into
mainfrom
feat/pp-url-scoping

Conversation

@AquiGorka

Copy link
Copy Markdown
Contributor

Summary

  • Migrate bundle submission to the URL-scoped, entity-scoped endpoint introduced by URL-scope every per-provider endpoint + signed-challenge entity submission provider-platform#107. pay-platform is the bundle submitter on behalf of the authenticated end-user, so the entity-scoped path (/providers/:pp/entity/bundles) is the correct vantage. Two call sites updated: pay/instant-submit.ts:145 (POS instant payment) and pay/instant-execute.ts:345 (self-custodial pay submission).
  • Fix deno task test so integration tests under tests/ load with the right --config tests/deno.json. The previous task was deno test --allow-all which ran every *_test.ts against the root config; integration tests import @electric-sql/pglite (only declared in tests/deno.json), so the root-config run errored on TS2307 before any test executed. New shape mirrors provider-platform's task: unit tests in src/ against the root config, integration tests in tests/ against tests/deno.json. 25 tests pass.

Why this is the right shape

provider-platform's URL-scoping splits /providers/:pp/bundles into two endpoints by auth principal:

  • bare path → provider-scoped (operator JWT + ownership) — operator's dashboard view of every bundle for this PP
  • /entity/bundles → entity-scoped (user/business JWT) — the calling submitter's own bundles

pay-platform submits bundles for the end-user as part of a payment flow. The JWT issued via auth/verify represents the user, not the PP operator. Entity-scoped is the natural choice.

Companion PRs

Test plan

  • deno fmt --check, deno lint, deno check src/main.ts, deno task test (25 pass — 13 unit + 12 integration)
  • test.sh pos-instant from local-dev against this branch — full POS instant payment flow green (5 OTEL checks: pay-platform 20 traces/167 spans, provider-platform 20 traces/176 spans, distributed tracing intact)

AquiGorka added 3 commits June 1, 2026 17:57
provider-platform's URL-scoping migration splits /providers/:pp/bundles into
two endpoints by auth principal: provider-scoped (operator JWT + ownership)
under /bundles, and entity-scoped (the bundle submitter's user/business JWT)
under /entity/bundles. pay-platform submits bundles on behalf of the
authenticated end-user, so the entity-scoped path is correct.

Two call sites updated: pay/instant-submit.ts:145 (POS instant payment) and
pay/instant-execute.ts:345 (self-custodial pay submission).
The default `deno task test` was `deno test --allow-all` which ran every
*_test.ts in the repo against the root config. Integration tests under
tests/ import @electric-sql/pglite (only declared in tests/deno.json), so
the root-config run errored on TS2307 before any test executed. Splitting
the task to run src/ and tests/ with their respective configs — same shape
as provider-platform's test task — lets both suites run cleanly.
AquiGorka added a commit to Moonlight-Protocol/local-dev that referenced this pull request Jun 1, 2026
## Summary

* Migrate every consumer in this repo to the URL-scoped surface
introduced by Moonlight-Protocol/provider-platform#107. Affected:
`e2e/dashboard.ts`
(channels/mempool/operations/treasury/metrics/audit-export),
`e2e/main.ts`, `e2e/pos-instant/main.ts`,
`e2e/governance/uc2-approve-reject.ts`,
`lifecycle/{main,testnet-verify,ci-test}.ts`, `testnet/main.ts`,
`setup-pp.{sh,ts}`, `lib/client/bundle.ts`. Comments and section headers
updated to name the new URLs.
* Add `lib/client/register-entity.ts` — a shared SEP-43/SEP-53
signed-challenge KYC submission helper, replacing six in-place
`registerEntity` copies across `e2e/`, `lifecycle/`, `testnet/` that hit
the bare-deleted `POST /api/v1/entities`. Living under `lib/client/` so
every docker-compose test-runner that already copies `/lib-src/client/`
reaches it.
* Rewrite `send-loop.ts:forceExpireBundles` to write `status='EXPIRED'`
+ back-dated TTL directly to the provider-platform PostgreSQL via
`postgres`. Replaces the bare-deleted HTTP admin endpoint `POST
/api/v1/dashboard/bundles/expire`; the back-dated TTL lets the
platform's mempool TTL-check sweep purge the in-memory slot via the same
path it uses for natural TTL expiry. There's a short race window: when
the executor wins the race to settle, the verifier later transitions the
bundle to COMPLETED; when our UPDATE + back-dated TTL win, the bundle
stays EXPIRED. In both cases the bundle reaches a terminal status, which
is what `INJECT_EXPIRE` exists to produce. (A race-free guarantee would
require in-process mempool access we deliberately don't expose over
HTTP.)

## Test-infra fix

* `docker-compose.pos-instant.yml`'s test-runner entrypoint only copied
top-level `lib/*.ts` into the container — it skipped the `lib/client/`
subdirectory the other suites already copy. After `register-entity.ts`
moved into `lib/client/`, the suite errored with `Module not found
"file:///app/lib/client/register-entity.ts"`. Adding `cp -r
/lib-src/client lib/` aligns pos-instant with the other suites' mount
shape; the import path becomes `./lib/client/register-entity.ts` to
match the container-flattened layout the other pos-instant imports use
(`./moonlight-pay-lib/…`, `./e2e/…`).

## URL changes (consumer side)

```
e2e/governance/lifecycle/testnet/setup-pp now hit:
  POST   /providers/:pp/entity/bundles                 (user-mode submit)
  GET    /providers/:pp/entity/bundles/:id             (user-mode poll)
  POST   /providers/:pp/council/join                   (operator)
  GET    /providers/:pp/council/membership             (operator)
  POST   /providers/:pp/council/membership             (operator sync)
  GET    /providers/:pp/{channels,mempool,operations,
         treasury,metrics,audit-export}                (operator dashboard)

entity registration uses lib/client/register-entity.ts:
  POST   /providers/:pp/entities/challenge             (issue nonce)
  POST   /providers/:pp/entities                       (signed submit)
```

## Companion PRs

- Moonlight-Protocol/provider-platform#107
- Moonlight-Protocol/provider-console#37
- Moonlight-Protocol/pay-platform#36

## Test plan
- [x] `deno fmt --check`, `deno lint` (23 pre-existing warnings, none
new)
- [x] `test.sh e2e`, `otel`, `governance`, `lifecycle`, `pos-instant`,
`invite-gate` — all green against the companion branches
- [x] `testnet/run-local.sh payment` + `lifecycle` — 23 OTEL checks
each, green
- [x] `send-loop.ts EXPIRE=true` — 12-country cycle, force-expire writes
EXPIRED to DB (`force-expired 1 of 1 bundle(s) via DB (TTL
back-dated…)`); at-rest DB shows EXPIRED for the one bundle whose UPDATE
landed before settlement and COMPLETED for the others (race outcome,
both terminal — see Summary)
@AquiGorka AquiGorka merged commit 1252c35 into main Jun 1, 2026
7 checks passed
@AquiGorka AquiGorka deleted the feat/pp-url-scoping branch June 1, 2026 23:13
AquiGorka added a commit to Moonlight-Protocol/local-dev that referenced this pull request Jun 2, 2026
…KYC route (#108)

## Summary

* Tighten the playwright full-flow suite so it actually fails when the
payment doesn't settle. The previous assertions accepted UI scaffolding
visibility regardless of whether anything landed protocol-side — the
suite passed against backends silently returning errors.
* Exercise the new public KYC route
(`#/entities/register?provider=<ppPubkey>`) introduced by
Moonlight-Protocol/provider-console#37: connect Freighter → sign
SEP-43/raw challenge → submit name → success card.
* Fix three pre-existing test bugs the loose assertions had hidden:
silent psql-based cleanup, stale `council_pps` accumulation, and a
wrong-pubkey fill in Step 8.

## Bugs the loose assertions hid

| # | Bug | Surfaced now via |
|---|-----|------------------|
| 1 | Step 12 asserted only `#pos-status` visible + non-empty.
moonlight-pay writes BOTH success and failure text into that element. |
Step 12 now reads the text and fails on
`failed|reject|error|expired|insufficient`. |
| 2 | Step 13 asserted "balance display OR tx list visible" — both
render on a freshly-loaded page regardless of any payment. | Step 13 now
queries `pay-platform.transactions` for a COMPLETED IN row with
`wallet_public_key = merchant.publicKey` and asserts
`sum(amount_stroops) > 0`. |
| 3 | `beforeAll` used `execSync("psql …")` to truncate stale rows —
psql isn't in the test-runner container, the call no-op'd ("DB cleanup
skipped"). | Replaced with `helpers/db.ts:cleanupPayPlatformDb()` using
node-pg, now truncating `councils`, `pay_accounts`, AND `council_pps`
CASCADE. Adds `pg` + `@types/pg` to `playwright/package.json`. |
| 4 | Step 8 filled `#pp-pk` with `profiles.provider.publicKey` (the
operator wallet pubkey), but provider-console registers a *derived* PP
keypair — pay-platform stored the wrong key and `instant-execute` 500'd
with "PP not found or inactive". | New Step 6b discovers the actual PP
pubkey via `helpers/db.ts:fetchActivePpPublicKey()`; Step 8 now fills it
with `ppPublicKey`. |
| 5 | `provider-platform`'s add-bundle gate requires the submitter's
entity row to be APPROVED. `pay-service` (the bundle submitter for
POS-instant) is auto-created as UNVERIFIED via SEP-10 first-time auth
and never promoted — bundle would 403. | Step 6b registers pay-service
as APPROVED via `helpers/register-entity.ts` (SEP-43/raw
signed-challenge POST to `/providers/:pp/entities`). |

## New surface

* `helpers/db.ts` — node-pg helpers: cleanup, PP discovery,
completed-inbound-stroops query.
* `helpers/register-entity.ts` — the SEP-43/raw signed-challenge flow
against `POST /providers/:pp/entities`.
* New steps:
- **Step 6b** (after council approval, before admin onboarding) —
discover PP pubkey + register pay-service as APPROVED.
- **Step 13b** (after merchant verification) — exercise the new public
KYC route end-to-end with the POS user's Freighter wallet.

## Test plan

- [x] `./test.sh playwright` — **16/16 steps pass** (was 14/14 passing
against a broken backend; now 16/16 passing against a working one).
Tested locally against the four PRs merged to main:
  - Moonlight-Protocol/provider-platform#107
  - Moonlight-Protocol/provider-console#37
  - Moonlight-Protocol/pay-platform#36
  - #107
- [x] Empirical confirmation of the original false-green: ran playwright
+ parallel `docker logs provider` capture; bundle submission 500'd with
`resolveChannelContext: PP GBOTA2… not found or inactive`,
`operations_bundles` table was empty, yet all 14 original steps passed.

## Companion / follow-up

None — this is a test-only PR on `local-dev`. No changes to platform
behaviour.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant