Skip to content

URL-scoped per-PP bundles, /entities KYC/KYB, per-PP event scoping - #106

Merged
AquiGorka merged 4 commits into
mainfrom
feat-provider-dashboard-rework
May 29, 2026
Merged

URL-scoped per-PP bundles, /entities KYC/KYB, per-PP event scoping#106
AquiGorka merged 4 commits into
mainfrom
feat-provider-dashboard-rework

Conversation

@AquiGorka

Copy link
Copy Markdown
Contributor

Summary

Three bundled changes for the provider-dashboard work:

  1. feat(entities) — rename usersentities, add POST /api/v1/entities for KYC/KYB-style approval, gate bundle submission on the submitter's entity being APPROVED (SUBMITTER_NOT_APPROVED).
  2. feat(bundles) — bundles submitted to /api/v1/providers/:ppPublicKey/bundles instead of channel-only. Each bundle row carries pp_public_key. channel-resolver requires explicit PP, returns the PP-bound signer/channel client. bundle.jurisdiction_from/to dropped (now derived from the submitter's entity). dashboard listRecentBundles is PP-scoped and returns entityName / jurisdictions / aggregated amount. Event emission moves from emitForChannel (every PP on the channel) to emitForBundles / emitForPp so dashboards only see events for bundles that belong to them.
  3. Version bump to 0.7.0 (minor — new surface area).

Migrations: 0014 (rename users→entities), 0015 (drop bundle jurisdictions), 0016 (bundle pp_public_key).

CI note

Cross-repo deadlock with local-dev and pay-platform:

Locally, with all three branches checked out, ./test.sh all is green across e2e + otel + governance + lifecycle + pos-instant.

Test plan

  • deno fmt --check (288 files)
  • deno lint (248 files)
  • deno task test:unit (64 passed)
  • deno task test:pay (136 passed)
  • Local ./test.sh all against local-dev + pay-platform branches — all 5 suites green

AquiGorka added 4 commits May 28, 2026 11:56
Renames the `users` table to `entities` to align with the KYC/KYB
concept Verified entities (people / legal entities) carry name and
jurisdictions. Adds POST /api/v1/entities to upsert an entity to
APPROVED status (idempotent on the public key).

- New `entities` table with name + jurisdictions columns
- `accounts.user_id` → `entity_id` with renamed FK constraint
- `UserRepository` → `EntityRepository`
- Public POST /api/v1/entities route
- create-challenge-db wires entities into the auth/account bootstrap
Bundles are now submitted to /providers/:ppPublicKey/bundles instead
of the channel-only route. The router extracts the PP, channel-resolver
loads that exact PP, verifies channel membership, and refuses any
default / first-match fallback. The bundle row stores pp_public_key so
downstream queries can scope cleanly.

- New URL-scoped routes for submit/get/list on /providers/:pp/bundles
- channel-resolver: ppPublicKey is required for writes; new
  resolveChannelClient for read-only paths (pay-platform balance)
- operations-bundle: add pp_public_key column, drop jurisdiction_from
  and jurisdiction_to (now derived from the submitter's entity)
- add-bundle: SUBMITTER_NOT_APPROVED gate, PP_NOT_MEMBER_OF_CHANNEL
  guard, pp_public_key recorded on persist
- SlotBundle / mempool events carry entityName, jurisdictions, amount
- emit helpers: emitForBundles + emitForAllPps; mempool / executor /
  verifier scope events to the bundles' owning PPs
- dashboard listRecentBundles: filtered by pp_public_key, returns
  entity name / jurisdictions / aggregated amount
AquiGorka added a commit to Moonlight-Protocol/local-dev that referenced this pull request May 29, 2026
…nstant (#103)

## Summary

Follow-up to #101 — fixes the harnesses that were missed because they
live next to (but aren't) the runners that the first PR touched:

- **`lifecycle/ci-test.ts`** is what the lifecycle Docker compose
actually invokes (`lifecycle/main.ts` is local-only). Now wires
`ppPublicKey` on Config and registers Alice/Bob via /api/v1/entities
before deposit/send. This is what unblocks **provider-platform's**
lifecycle CI.
- **`e2e/governance/uc2-approve-reject.ts`** still read
`.councilMembership` (singular) on the dashboard /pp/list response;
provider-platform PR #103 had renamed it to `.councilMemberships`
(plural array) months ago. Updated reads to `[0]?.status` / `.length ===
0`.
- **`e2e/pos-instant/main.ts`** registers `keys.payService` as APPROVED
before the instant payment flow, since pay-platform authenticates to
provider-platform under that key and otherwise trips the
SUBMITTER_NOT_APPROVED gate.
- **`test/setup-e2e.ts`** writes `E2E_PP_PUBLIC_KEY` to
/config/contracts.env. (`e2e/setup.sh` already had this in the CI flow;
the local `./test.sh e2e` flow uses the test/ setup script and needs the
same line.)

## Test plan

Locally (`./test.sh all`), all 5 suites green: e2e, otel, governance,
lifecycle, pos-instant.

After this merges, re-running provider-platform's PR CI
(Moonlight-Protocol/provider-platform#106)
should flip lifecycle from red to green.
@AquiGorka
AquiGorka merged commit 769e04f into main May 29, 2026
16 of 19 checks passed
@AquiGorka
AquiGorka deleted the feat-provider-dashboard-rework branch May 29, 2026 13:48
AquiGorka added a commit that referenced this pull request Jun 1, 2026
…ssion (#107)

## Summary

* URL-scope every per-provider HTTP endpoint under
`/api/v1/providers/:ppPublicKey/...`. Handlers read the PP from the path
only — no JWT-derived/body-field/query-string fallback, no default PP. A
`requirePpOwnership` middleware verifies the operator JWT subject owns
the URL's PP before any handler runs (also closes the cross-PP bundle
leak the previous unscoped `/dashboard/bundles/:id` had).
* Split the `/providers/:pp/bundles` family by auth principal: bare
`/providers/:pp/bundles` is provider-scoped (operator JWT + ownership;
returns all bundles for that PP joined to the submitter entity for
`entityName` + `jurisdictions` display);
`/providers/:pp/entity/bundles{,/:bundleId}` is entity-scoped (the
bundle submitter's user/business JWT). Same noun, opposite vantages —
the URL prefix communicates the auth principal so callers don't need
verb/JWT-shape isolated knowledge.
* Public KYC/KYB submission with SEP-43/SEP-53/raw signed challenge:
`POST /providers/:pp/entities/challenge` issues a single-use nonce,
`POST /providers/:pp/entities` accepts `{pubkey, name, jurisdictions,
signedChallenge:{nonce,signature}}` and creates / promotes the entity to
APPROVED. Server-side name sanitisation (strip HTML tags, collapse
whitespace) is the authoritative gate. Replaces the previous
unauthenticated, unscoped `POST /api/v1/entities`.

## Surface change

```
NEW (provider-scoped, JWT + requirePpOwnership):
  DELETE /providers/:pp                     (was POST /dashboard/pp/delete)
  GET    /providers/:pp/channels            (was GET /dashboard/channels)
  GET    /providers/:pp/mempool             (was GET /dashboard/mempool)
  GET    /providers/:pp/operations          (was GET /dashboard/operations)
  GET    /providers/:pp/treasury            (was GET /dashboard/treasury?ppPublicKey=…)
  GET    /providers/:pp/utxos               (was GET /dashboard/utxos?ppPublicKey=…&channelContractId=…)
  GET    /providers/:pp/transactions[/:id]  (was GET /dashboard/transactions[/:id]?ppPublicKey=…)
  GET    /providers/:pp/bundles             (was GET /dashboard/bundles?ppPublicKey=…)
  GET    /providers/:pp/bundles/:id         (was GET /dashboard/bundles/:id — closes the cross-PP leak)
  GET    /providers/:pp/audit-export        (was GET /dashboard/audit-export — adds per-PP filter)
  GET    /providers/:pp/metrics             (was GET /dashboard/metrics?ppPublicKey=…)
  POST   /providers/:pp/council/join        (was POST /dashboard/council/join, body.ppPublicKey)
  GET    /providers/:pp/council/membership  (was GET /dashboard/council/membership?ppPublicKey=…)
  POST   /providers/:pp/council/membership  (was POST /dashboard/council/membership, body.ppPublicKey)
  GET    /providers/:pp/events/ws           (was GET /events/ws?pp=…)

MOVED (entity-scoped, end-user JWT — PR #106's user-mode endpoints land here):
  POST   /providers/:pp/entity/bundles                (was POST /providers/:pp/bundles)
  GET    /providers/:pp/entity/bundles                (was GET  /providers/:pp/bundles)
  GET    /providers/:pp/entity/bundles/:bundleId      (was GET  /providers/:pp/bundles/:bundleId)

NEW (public, no JWT, signed challenge required):
  POST   /providers/:pp/entities/challenge
  POST   /providers/:pp/entities

DEPRECATED (410 Gone, body names the new URL):
  POST /dashboard/pp/delete
  GET  /dashboard/{channels,mempool,operations,treasury,utxos,
        transactions[/:id],bundles/:id,audit-export,metrics}
  POST /dashboard/council/join
  GET  /dashboard/council/membership
  POST /dashboard/council/membership
  POST /api/v1/entities
  GET  /api/v1/events/ws

BARE-DELETED (404; no callers):
  POST /dashboard/bundles/expire
  GET  /dashboard/bundles (query-string variant)

UNCHANGED (not per-PP, intentionally):
  POST /dashboard/auth/{challenge,verify}   (operator wallet auth)
  POST /dashboard/pp/register               (PP doesn't exist yet)
  GET  /dashboard/pp/list                   (operator's multi-PP view)
  POST /dashboard/council/discover          (operates on councilUrl)
  POST /stellar/auth, GET /stellar/auth     (end-user SEP-10)
  /pay/*, /waitlist, /health
```

## Consumer impact

- `provider-console`, `pay-platform`, and `local-dev` migrate in
companion PRs in the same unit of work.
- `browser-wallet` (Torugo's) still hits the pre-PR-#106 flat
`/api/v1/bundle` and is broken against \`main\` already; new endpoints
they need to migrate to: bundle POST/list/detail at
`/api/v1/providers/:ppPublicKey/entity/bundles{,/:bundleId}`. SEP-10
stays at `/api/v1/stellar/auth`.

## Test plan
- [x] `deno fmt --check`, `deno lint`, `deno check src/main.ts`, `deno
task test` (136 unit/pay tests pass)
- [x] Integration tests for migrated paths updated against new router
(19 pass)
- [x] Local stack via `local-dev`: `up.sh` + `setup-c.sh` +
`setup-pp.sh` succeed; full smoke matrix green (provider-scoped 200s,
entity-scoped 401 for operator JWT, 410 stubs name new URLs,
bare-deletes 404, KYC challenge + signed submit creates entity,
server-side XSS sanitisation strips `<script>` from name)
- [x] `test.sh e2e`, `otel`, `governance`, `lifecycle`, `pos-instant`,
`invite-gate` all pass against this branch
- [x] `testnet/run-local.sh payment` + `testnet/run-local.sh lifecycle`
against this branch (23 OTEL checks each)
AquiGorka added a commit that referenced this pull request Jun 8, 2026
…ard / entities / events surface (#112)

Follow-up to #106.

## Summary

PR #106 migrated every dashboard / entities / events route to the per-PP
`/api/v1/providers/:ppPublicKey/...` shape and left 410 Gone stubs at
the old un-scoped paths so callers could migrate. The migration is
settled and no caller in any consumer repo still hits the un-scoped
paths, so the stubs are dropped — the router now answers 404 (route not
found) for those URLs.

## What changes

- Delete the 16 deprecation stubs:
- 14 in `src/http/v1/dashboard/routes.ts` (`POST /dashboard/pp/delete`,
`GET
/dashboard/{channels,mempool,operations,treasury,utxos,transactions,transactions/:id,bundles/:id,audit-export,metrics,council/membership}`,
`POST /dashboard/council/{join,membership}`)
- `POST /entities` (un-scoped) — the entire
`src/http/v1/entities/routes.ts` becomes empty and is deleted
- `GET /events/ws` (un-scoped) — the entire
`src/http/v1/events/routes.ts` becomes empty and is deleted
- Remove the `buildEntitiesRouter` + `buildEventsRouter` imports and
mounts from `src/http/v1/v1.routes.ts`.
- Remove the orphan deprecation comment block + the sibling
already-bare-deleted-routes note in `src/http/v1/dashboard/routes.ts`.
- Remove the three integration tests that asserted the 410 behavior of
the deleted stubs (`bundle-admin.test.ts`, `dashboard-metrics.test.ts`,
`events-ws.test.ts`).
- Bump version to `0.7.3`.

The live per-PP handlers (`dashboard/*.ts`, `entities/post.ts`,
`entities/challenge.ts`, `events/ws-handler.ts`) are unchanged and stay
mounted via `providers/routes.ts`.

## Test plan

- [x] `deno fmt --check` clean
- [x] `deno lint` clean
- [x] `deno task test:unit` — 64 passed / 0 failed
- [x] `deno task test:integration` — 96 passed / 0 failed (99 → 96 is
the 3 deleted 410-stub tests, no regressions)
- [x] Curl every old path against a locally-running build — all 16
return 404 (not 410)
- [x] Curl per-PP replacement paths (`channels`, `metrics`, `treasury`,
`entities`) — all respond live (401 from `jwtMiddleware` on the
protected ones; the public `entities` POST hits `requirePpExists` and
answers `Provider not found`)
- [x] Grep all consumer repos (`provider-console`, `council-console`,
`network-dashboard`, `network-dashboard-platform`, `moonlight-pay`,
`browser-wallet`, `moonlight-sdk`, `local-dev`) — zero non-test call
sites on any of the 16 deleted paths
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