What the two companion PRs change across the backend service and the backoffice, with database, API and application-interaction diagrams.
feat/arthur-providers
targets develop / merlin
integration › domain › action leaf with its own pre-defined flow; a new Arthur backoffice app manages providers, and the Merlin flow editor gains a “Call Provider” action that materialises a per-partner internal clone of the provider’s flow at save time and keeps it in sync.
One new collection (providers) and new opt-in fields on the shared flow schema. The pre-defined-flows and partner-flows collections share one schema factory (buildFlowSchema); the PR adds a kind marker to the pre-defined side only and internal/provider fields to the partner side only.
| Migration | Collection | Index | Why |
|---|---|---|---|
20261016-providers-indexes | providers | unique (integration, domain, action) | One provider per leaf; a duplicate insert surfaces as Mongo E11000 → mapped to 409. |
20261019-provider-internal-flow-indexes | partner-flows | sparse provider.providerId | Cheap lookup of every internal flow backing a provider (delete-guard, sync). |
unique (partnerId, provider.providerId) · partial internal:true | At most one internal clone per partner × provider — closes the concurrent-save race (loser gets E11000 and reuses the winner). |
Both migrations are idempotent (the schema also declares the indexes for autoIndex environments); migrations run on boot so a fresh DB gets them deterministically.
A new NestJS providers module exposes CRUD + sync under /api/v1/providers. Coordinates are immutable; actionName is derived as provider__<integration>__<domain>__<action>.
| Method | Route | Behaviour |
|---|---|---|
| GET | /api/v1/providers | List all providers, sorted by integration, domain, action. |
| GET | /api/v1/providers/:id | Get one; ?withFlow=true attaches the backing flow (same shape as GET /pre-defined-flow/:id). |
| POST | /api/v1/providers | Create a provider — does not create a flow. Blank name → 400; duplicate leaf → 409. |
| PUT | /api/v1/providers/:id | Update name/description and/or save the backing flow in one call (cleans up the flow if the second write fails). Coordinates are stripped by the whitelist pipe. |
| DELETE | /api/v1/providers/:id | 204 on success; 409 while partners use it (message names them). Provider removed before its flow. |
| POST | /api/v1/providers/:id/sync-partners | Re-sync every partner internal flow to the provider’s current flow. ?dryRun is a strict boolean — anything but true/false → 400. |
flowKey / actionName is deterministic: provider__<integration>__<domain>__<action> — byte-identical on both the service and backoffice sides.Editing a provider’s flow does not touch partners immediately — each partner holds its own cloned internal flow, tagged with the contentHash of the provider flow it was copied from. A provider edit propagates to those clones in one of two ways; drift is always detected by comparing hashes.
contentHash excludes name/actionName, so cosmetic edits never trigger a rewrite. Engine push on Path A is per-clone; order (internal before parent) is fire-and-forget.Provider _ids differ between environments, so an exported bundle cannot carry them. On import, every Call Provider action is re-matched to the target environment’s provider by coordinates and its internal flow is re-materialised locally.
| File | Role | |
|---|---|---|
| new | src/providers/providers.controller.ts | The 6 routes above. |
| new | src/providers/providers.service.ts | CRUD, 404/409/400 mapping, orchestrates the flow save. |
| new | src/providers/provider-snapshot.service.ts | Create/reuse/overwrite/GC of per-partner internal clones. |
| new | src/providers/provider-flow-cloner.ts | Deep copy of a flow (groups, gateways, conditions, deps) with reassigned ids. |
| new | src/providers/provider-content-hash.ts | Stable content hash used to detect drift. |
| new | src/providers/providers.schema.ts | The providers collection + unique index. |
| mod | src/flow/schemas/flow-base.schema.ts | Opt-in kind (pre-defined) and internal/provider (partner) fields + indexes. |
| mod | src/flow/flow.service.ts / flow.repository.ts | Save hooks, findByProviderId, kind filtering. |
| mod | src/partner-import-export/writers/merlin-config.writer.ts | Resolves providers by coordinates and re-materialises internal flows on import; replace also deletes old internal flows. |
| mod | src/partner-import-export/components/flow-definitions.component.ts | Drops the bundle’s provider__ internal definitions and pushes the freshly materialised ones after the engine clear. |
A brand-new Nx app apps/arthur (basePath /arthur, port 3019) is mounted into the shell, plus a “Call Provider” action added to the existing Merlin flow editor.
/arthur/* to the new app and /api/merlin/* to the service. The Merlin editor reads the provider list to build the Call Provider cascade.Dedicated ARTHUR.PROVIDER.* Keycloak roles do not exist yet (PR 10), so the three Arthur roles alias Merlin Settings roles 1:1. Only this one file changes when the real roles land.
| Arthur role | Aliases | Grants |
|---|---|---|
ARTHUR | MERLIN. (prefix) | Shell card + any Arthur access. |
ARTHUR_PROVIDER_VIEW | MERLIN.SETTINGS.VIEW | List, read, open edit page read-only. |
ARTHUR_PROVIDER_MANAGE | MERLIN.SETTINGS.MANAGE | Create, edit, delete, sync. |
integration / domain / action slug + name/description → POST /providers.FlowTimelineView; coordinates are read-only → PUT /providers/:id saves provider + flow together.dryRun preview of would-be updates, then apply.internal flow and points the item at it.ARTHUR_ENABLED=false per environment until rollout and set ARTHUR_BACKOFFICE_URL. The frontend PR needs the companion service PR. Export/import of partners using Call Provider is now handled (resolved by coordinates on import — see §2). Remaining follow-ups: engine push order (internal before parent) is fire-and-forget, not awaited; ARTHUR.* Keycloak roles come in PR 10 (backoffice aliases MERLIN.SETTINGS for now).
Generated from the two PRs’ descriptions and the feat/arthur-providers source (service rev f742446, import-by-coordinates). Service suite: 1675 passed / 9 skipped, plus a 25/25 import E2E. Diagrams are schematic, not exhaustive.