URL-scoped per-PP bundles, /entities KYC/KYB, per-PP event scoping - #106
Merged
Conversation
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.
5 tasks
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)
7 tasks
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three bundled changes for the provider-dashboard work:
feat(entities)— renameusers→entities, add POST /api/v1/entities for KYC/KYB-style approval, gate bundle submission on the submitter's entity being APPROVED (SUBMITTER_NOT_APPROVED).feat(bundles)— bundles submitted to/api/v1/providers/:ppPublicKey/bundlesinstead of channel-only. Each bundle row carriespp_public_key. channel-resolver requires explicit PP, returns the PP-bound signer/channel client.bundle.jurisdiction_from/todropped (now derived from the submitter's entity). dashboardlistRecentBundlesis PP-scoped and returns entityName / jurisdictions / aggregated amount. Event emission moves fromemitForChannel(every PP on the channel) toemitForBundles/emitForPpso dashboards only see events for bundles that belong to them.Migrations: 0014 (rename users→entities), 0015 (drop bundle jurisdictions), 0016 (bundle pp_public_key).
CI note
Cross-repo deadlock with
local-devandpay-platform:local-dev@mainwhich still calls the old/api/v1/bundleroute — will be red until local-dev's PR (Update e2e/lifecycle harnesses + setup for URL-scoped bundles local-dev#101) merges.Locally, with all three branches checked out,
./test.sh allis green across e2e + otel + governance + lifecycle + pos-instant.Test plan
./test.sh allagainst local-dev + pay-platform branches — all 5 suites green