@wildo-ai/platform-infra-providers 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +34 -0
  2. package/dist/esm/aws-s3/aws-s3.infra.module.d.ts +63 -0
  3. package/dist/esm/aws-s3/aws-s3.infra.module.d.ts.map +1 -0
  4. package/dist/esm/aws-s3/aws-s3.infra.module.js +110 -0
  5. package/dist/esm/aws-s3/aws-s3.infra.module.js.map +1 -0
  6. package/dist/esm/clamav-self/clamav-self.infra.module.d.ts +30 -0
  7. package/dist/esm/clamav-self/clamav-self.infra.module.d.ts.map +1 -0
  8. package/dist/esm/clamav-self/clamav-self.infra.module.js +61 -0
  9. package/dist/esm/clamav-self/clamav-self.infra.module.js.map +1 -0
  10. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts +26 -0
  11. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts.map +1 -0
  12. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js +54 -0
  13. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js.map +1 -0
  14. package/dist/esm/index.d.ts +88 -0
  15. package/dist/esm/index.d.ts.map +1 -0
  16. package/dist/esm/index.js +125 -0
  17. package/dist/esm/index.js.map +1 -0
  18. package/dist/esm/minio-self/minio-self.infra.module.d.ts +64 -0
  19. package/dist/esm/minio-self/minio-self.infra.module.d.ts.map +1 -0
  20. package/dist/esm/minio-self/minio-self.infra.module.js +105 -0
  21. package/dist/esm/minio-self/minio-self.infra.module.js.map +1 -0
  22. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts +52 -0
  23. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts.map +1 -0
  24. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js +201 -0
  25. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js.map +1 -0
  26. package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts +81 -0
  27. package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts.map +1 -0
  28. package/dist/esm/mongodb-self/mongodb-self.infra.module.js +311 -0
  29. package/dist/esm/mongodb-self/mongodb-self.infra.module.js.map +1 -0
  30. package/dist/esm/postgres-self/postgres-self.infra.module.d.ts +70 -0
  31. package/dist/esm/postgres-self/postgres-self.infra.module.d.ts.map +1 -0
  32. package/dist/esm/postgres-self/postgres-self.infra.module.js +328 -0
  33. package/dist/esm/postgres-self/postgres-self.infra.module.js.map +1 -0
  34. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts +88 -0
  35. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts.map +1 -0
  36. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js +473 -0
  37. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js.map +1 -0
  38. package/dist/esm/redis-self/redis-self.infra.module.d.ts +89 -0
  39. package/dist/esm/redis-self/redis-self.infra.module.d.ts.map +1 -0
  40. package/dist/esm/redis-self/redis-self.infra.module.js +331 -0
  41. package/dist/esm/redis-self/redis-self.infra.module.js.map +1 -0
  42. package/dist/tsconfig.build.tsbuildinfo +1 -0
  43. package/package.json +52 -0
  44. package/src/aws-s3/__tests__/aws-s3.infra.module.test.ts +67 -0
  45. package/src/aws-s3/aws-s3.infra.module.ts +120 -0
  46. package/src/clamav-self/__tests__/clamav-self.infra.module.test.ts +65 -0
  47. package/src/clamav-self/clamav-self.infra.module.ts +72 -0
  48. package/src/gotenberg-self/__tests__/gotenberg-self.infra.module.test.ts +65 -0
  49. package/src/gotenberg-self/gotenberg-self.infra.module.ts +65 -0
  50. package/src/index.ts +133 -0
  51. package/src/minio-self/__tests__/minio-self.infra.module.test.ts +70 -0
  52. package/src/minio-self/minio-self.infra.module.ts +116 -0
  53. package/src/mongodb-atlas/__tests__/mongodb-atlas.infra.module.test.ts +198 -0
  54. package/src/mongodb-atlas/mongodb-atlas.infra.module.ts +236 -0
  55. package/src/mongodb-self/__tests__/mongodb-self.infra.module.test.ts +419 -0
  56. package/src/mongodb-self/mongodb-self.infra.module.ts +384 -0
  57. package/src/postgres-self/__tests__/postgres-self.infra.module.test.ts +344 -0
  58. package/src/postgres-self/postgres-self.infra.module.ts +377 -0
  59. package/src/rabbitmq-self/__tests__/rabbitmq-self.infra.module.test.ts +499 -0
  60. package/src/rabbitmq-self/rabbitmq-self.infra.module.ts +531 -0
  61. package/src/redis-self/__tests__/redis-self.infra.module.test.ts +341 -0
  62. package/src/redis-self/redis-self.infra.module.ts +372 -0
@@ -0,0 +1,328 @@
1
+ /**
2
+ * `PostgresSelfInfraProviderModule` — third concrete `InfraProviderModule`
3
+ * (Slice 4.B.2 of `infra-provider-planner.md`, generalizing the
4
+ * Slice 2 / 4.B.1 pattern).
5
+ *
6
+ * Implements the `POSTGRES_SELF` `InfraProvider` over the
7
+ * `IPostgresAdminClient` transport (raw `pg` Pool against the
8
+ * Postgres admin connection, exposed by
9
+ * `PostgreSQLConnectionManagerService` after the Slice 4.B.2 5C
10
+ * policy/transport split). Owns ALL per-app naming policy — the
11
+ * per-app database-name + role-name conventions (`wildo_<sanitized>_user`,
12
+ * `wildo_<sanitized>` mirroring the legacy
13
+ * `PlatformInfrastructureManagerService.provisionPostgreSQLDatabase`).
14
+ *
15
+ * **What this module reads from `AppCredentialMintingContext`**:
16
+ * - `appSlug` — used as the source for the sanitized database +
17
+ * user name. Mirrors the legacy convention at
18
+ * `platform-infrastructure-manager.service.ts:300-302`
19
+ * (`sanitizedName = sanitizeForPostgreSQL(applicationName)`,
20
+ * `databaseName = wildo_${sanitizedName}`,
21
+ * `username = wildo_${sanitizedName}_user`).
22
+ * - `secretInputs['database.postgresql.password']` — orchestrator-
23
+ * supplied password (module is upsert-only, never generator).
24
+ * - `applicationConfigInputs?.['databaseName']` — optional override
25
+ * for the derived database name. Mirrors the legacy
26
+ * `provisionPostgreSQLDatabase(appSlug, { databaseName? })` arg.
27
+ * - `adminClient` — narrowed to `IPostgresAdminClient`.
28
+ *
29
+ * **What this module IGNORES from the context** (Slice 4.A.2 / M4
30
+ * forward-compat documentation):
31
+ * - `applicationNamespace` — Postgres per-app DB names use the
32
+ * slug-derived sanitized form (env-suffix isolation comes from
33
+ * the surrounding env-scoped admin connection). Future modules
34
+ * may switch to namespace-scoped naming if cross-env collision
35
+ * risk surfaces.
36
+ * - `wildoApplicationId` — Postgres role names embed the
37
+ * sanitized slug, NOT the application id. This is the legacy
38
+ * convention; preserved for back-compat with apps already
39
+ * provisioned.
40
+ *
41
+ * **Two audiences this module serves:**
42
+ *
43
+ * 1. **Audience-1 (platform-admin, declared via `plan().platformAdmin`)**:
44
+ * cluster-wide Postgres admin creds. Consumers: `apps-manager`
45
+ * (mints per-app DB+role via `mintAppCredentials`), `companion`
46
+ * (mirrors apps-manager admin role for local dev).
47
+ *
48
+ * 2. **Audience-3 (app-scoped, via `plan().appRuntime` +
49
+ * `mintAppCredentials`)**: per-customer-app Postgres database +
50
+ * role. Result threaded into
51
+ * `Wildo_Application_Secrets.database.postgresql.{
52
+ * databaseName, username, connectUrl}` via
53
+ * `_merge_WildoApplicationSecrets`. Note: the password is
54
+ * embedded in `connectUrl` — there is no separate
55
+ * `database.postgresql.password` blob path today (matches the
56
+ * legacy persistence shape at
57
+ * `wildo-application-lifecycle.service.ts:2191-2200`).
58
+ *
59
+ * **NOT served (D-4 of Slice 4.B locked decisions, 2026-05-10)**:
60
+ * - **Audience-2 (platform-service-scoped)**: no platform service
61
+ * uses Postgres for inter-service messaging today. Method omitted.
62
+ *
63
+ * **`revokeAppCredentials`**: declared and tested, but the integration
64
+ * call site does NOT exist in `wildo-application-lifecycle.service.ts`
65
+ * (no app-deletion ceremony today — F6 of plan review 2026-05-06).
66
+ * Method ships as a forward-compat seam.
67
+ */
68
+ import { InfraCapability, InfraPlatformAdminConsumer, InfraProvider, ApplicationRuntimePrincipalKind, resolveGrantStorageNames, } from '@wildo-ai/platform-config-lib';
69
+ /**
70
+ * Default Postgres port (matches `INFRA_DEFAULT_PORTS.POSTGRES` in
71
+ * the shared platform constants — duplicated as a literal because
72
+ * the planner output is consumed by the CLI which materializes it
73
+ * into `Wildo_Application_Config` blob paths).
74
+ */
75
+ const DEFAULT_POSTGRES_PORT = 5432;
76
+ /**
77
+ * The `Wildo_Application_Secrets` JSON paths this module writes
78
+ * through `mintAppCredentials`. Declared once and reused as
79
+ * `appRuntime.perAppSecretBlobPaths` in `plan()` AND as the keys of
80
+ * `secretBlobValues` in the result, so a refactor that renames a
81
+ * path fails loudly at one place.
82
+ *
83
+ * Note: the password is embedded in `connectUrl` (Postgres
84
+ * convention); there is no separate `database.postgresql.password`
85
+ * blob path today. The orchestrator persists `connectUrl`
86
+ * verbatim into `Wildo_Application_Secrets`.
87
+ */
88
+ const POSTGRES_DATABASE_NAME_PATH = 'database.postgresql.databaseName';
89
+ const POSTGRES_USERNAME_PATH = 'database.postgresql.username';
90
+ const POSTGRES_CONNECT_URL_PATH = 'database.postgresql.connectUrl';
91
+ /**
92
+ * The paths this module mints PER RUNTIME PRINCIPAL — the single source for both consumers.
93
+ *
94
+ * Read by `InfraProviderModule.perPrincipalSecretBlobPaths` (statically, so apps-manager can strip
95
+ * these from the application blob before delivering a principal's own material) AND spread into
96
+ * `appRuntime.perAppSecretBlobPaths` inside `plan()`. Declaring the list twice is how the delivery
97
+ * path would silently stop covering a path this module still mints.
98
+ */
99
+ const PER_PRINCIPAL_SECRET_BLOB_PATHS = [
100
+ POSTGRES_DATABASE_NAME_PATH,
101
+ POSTGRES_USERNAME_PATH,
102
+ POSTGRES_CONNECT_URL_PATH,
103
+ ];
104
+ /**
105
+ * Key the module looks for in `secretInputs` to read the
106
+ * orchestrator-supplied password.
107
+ */
108
+ const POSTGRES_PASSWORD_INPUT_KEY = 'database.postgresql.password';
109
+ /**
110
+ * Optional key the module looks for in `applicationConfigInputs`
111
+ * for an authored database-name override. When omitted, the module
112
+ * derives the database name from `appSlug` via `sanitizeForPostgreSQL`.
113
+ */
114
+ const POSTGRES_DATABASE_NAME_INPUT_KEY = 'databaseName';
115
+ /**
116
+ * Sanitize an application slug for Postgres identifier rules.
117
+ * Mirrors the legacy `sanitizeForPostgreSQL` at
118
+ * `platform-infrastructure-manager.service.ts:371-378`:
119
+ * - lowercase
120
+ * - `[^a-z0-9_]` → `_`
121
+ * - collapse multiple `_`
122
+ * - strip leading/trailing `_`
123
+ * - cap at 50 chars (Postgres identifier limit is 63; leaves room
124
+ * for the `wildo_` prefix and `_user` suffix).
125
+ *
126
+ * Centralized here so the module owns the convention; if the cap
127
+ * or escape rules change, one site changes.
128
+ */
129
+ function sanitizeForPostgres(value) {
130
+ return value
131
+ .toLowerCase()
132
+ .replace(/[^a-z0-9_]/g, '_')
133
+ .replace(/_+/g, '_')
134
+ .replace(/^_|_$/g, '')
135
+ .substring(0, 50);
136
+ }
137
+ /**
138
+ * Compute the per-app database name. Mirrors legacy convention at
139
+ * `platform-infrastructure-manager.service.ts:301`:
140
+ * `wildo_<sanitized>`
141
+ */
142
+ function buildDatabaseName(appSlug) {
143
+ return `wildo_${sanitizeForPostgres(appSlug)}`;
144
+ }
145
+ /**
146
+ * Compute the per-app database role. Mirrors legacy convention at
147
+ * `platform-infrastructure-manager.service.ts:302`:
148
+ * `wildo_<sanitized>_user`
149
+ */
150
+ function buildUsername(appSlug) {
151
+ return `wildo_${sanitizeForPostgres(appSlug)}_user`;
152
+ }
153
+ /**
154
+ * The schema a per-app database's resource tables live in.
155
+ *
156
+ * `public` is what the schema planner targets today. Named as a constant rather
157
+ * than inlined at the four sites that need it because a grant issued into one
158
+ * schema and revoked from another is a revocation that silently succeeds while
159
+ * narrowing nothing.
160
+ */
161
+ const POSTGRES_APPLICATION_SCHEMA = 'public';
162
+ /**
163
+ * Per-principal Postgres role name — the SAME derivation the revoke path uses.
164
+ *
165
+ * Fourth time this shape appears (Redis, RabbitMQ, Mongo, now Postgres), and the
166
+ * consequence is worse here than anywhere else: a revoke that derived the name
167
+ * differently would miss the minion's role AND, on the pre-existing
168
+ * `dropApplicationDatabase` path, take the application's entire database with
169
+ * it. The application backend keeps `wildo_<slug>_user` VERBATIM so an existing
170
+ * deployment's owner role is never orphaned.
171
+ */
172
+ function buildPostgresPrincipalUsername(appSlug, principal) {
173
+ if (!principal || principal.kind === ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {
174
+ return buildUsername(appSlug);
175
+ }
176
+ return `wildo_${sanitizeForPostgres(appSlug)}_${sanitizeForPostgres(principal.kind)}_`
177
+ + `${sanitizeForPostgres(principal.name ?? '')}_user`;
178
+ }
179
+ export const PostgresSelfInfraProviderModule = {
180
+ provider: InfraProvider.POSTGRES_SELF,
181
+ supportedCapabilities: [InfraCapability.RELATIONAL_DATABASE],
182
+ perPrincipalSecretBlobPaths: PER_PRINCIPAL_SECRET_BLOB_PATHS,
183
+ plan() {
184
+ return {
185
+ dockerComposeServices: ['postgres'],
186
+ k8sResources: ['templates/infra/k8s/internal/postgres/deployment.yaml.hbs'],
187
+ platformAdmin: {
188
+ envVars: {
189
+ PG_HOST: 'postgres',
190
+ PG_PORT: String(DEFAULT_POSTGRES_PORT),
191
+ },
192
+ // Admin user + password to provision per-app DBs/roles. The
193
+ // legacy admin-init code reads these from `PG_ADMIN_USER` /
194
+ // `PG_ADMIN_PASSWORD`; the planner emits the keys, the
195
+ // CLI's `env-vars-builder` writes the values at sync time.
196
+ secretEnvVars: ['PG_ADMIN_USER', 'PG_ADMIN_PASSWORD'],
197
+ // Audience-1 only (DB-admin access). No audience-2
198
+ // platform-service-scoped Postgres use case today (D-4).
199
+ consumers: [
200
+ InfraPlatformAdminConsumer.APPS_MANAGER,
201
+ InfraPlatformAdminConsumer.COMPANION,
202
+ ],
203
+ },
204
+ appRuntime: {
205
+ // Static topology — same for every app in this env. The
206
+ // per-app values (databaseName, username, connectUrl) flow
207
+ // through `perAppSecretBlobPaths`.
208
+ perAppConfigBlobPaths: {
209
+ 'database.postgresql.host': 'postgres',
210
+ 'database.postgresql.port': DEFAULT_POSTGRES_PORT,
211
+ },
212
+ perAppSecretBlobPaths: [...PER_PRINCIPAL_SECRET_BLOB_PATHS],
213
+ },
214
+ };
215
+ },
216
+ async mintAppCredentials(context) {
217
+ const adminClient = context.adminClient;
218
+ const password = context.secretInputs[POSTGRES_PASSWORD_INPUT_KEY];
219
+ if (!password) {
220
+ throw new Error(`PostgresSelfInfraProviderModule.mintAppCredentials: ` +
221
+ `secretInputs is missing '${POSTGRES_PASSWORD_INPUT_KEY}'. ` +
222
+ `The orchestrator MUST supply the Postgres password — modules are upsert-only.`);
223
+ }
224
+ if (!context.appSlug) {
225
+ throw new Error(`PostgresSelfInfraProviderModule.mintAppCredentials: ` +
226
+ `appSlug is empty. ` +
227
+ `The orchestrator MUST supply a non-empty slug.`);
228
+ }
229
+ // Allow operator override of the derived database name. Without
230
+ // an override, derive from `appSlug` via the legacy convention.
231
+ const databaseName = context.applicationConfigInputs?.[POSTGRES_DATABASE_NAME_INPUT_KEY]
232
+ || buildDatabaseName(context.appSlug);
233
+ const username = buildPostgresPrincipalUsername(context.appSlug, context.principal);
234
+ if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {
235
+ // A named principal gets a TABLE-SCOPED role (Step 3 / R1 / D14): exactly
236
+ // the verbs its `resourceAccess` derived, on exactly the tables that
237
+ // declaration names, inside a database it does not own.
238
+ //
239
+ // `resourceGrants` is REQUIRED and its absence is an error rather than a
240
+ // default. Falling through to `upsertApplicationDatabase` would make the
241
+ // minion the OWNER of the application's database with `ALL PRIVILEGES` —
242
+ // the exact over-grant this path replaces, arriving silently through a
243
+ // code path that reads as if it were doing the narrow thing.
244
+ const grants = context.resourceGrants;
245
+ if (!grants) {
246
+ throw new Error(`PostgresSelfInfraProviderModule.mintAppCredentials: `
247
+ + `principal '${context.principal.kind}:${context.principal.name}' has no resourceGrants. `
248
+ + `Refusing to fall back to the database-wide application role — a minion with no declared `
249
+ + `access must get NO database role at all (D11/R3), which is the orchestrator's decision to make.`);
250
+ }
251
+ const { connectionUrl: principalConnectionUrl } = await adminClient.upsertTableScopedDatabaseRole({
252
+ databaseName,
253
+ username,
254
+ password,
255
+ schemaName: POSTGRES_APPLICATION_SCHEMA,
256
+ // Resource types → table names HERE, at the adapter that speaks
257
+ // Postgres. The grant contract itself stays in the platform vocabulary,
258
+ // and passes through the SAME translation Mongo uses, so the two
259
+ // adapters cannot disagree about which rows a resource type addresses.
260
+ readTables: resolveGrantStorageNames(grants.read),
261
+ writeTables: resolveGrantStorageNames(grants.write),
262
+ });
263
+ return {
264
+ secretBlobValues: {
265
+ [POSTGRES_DATABASE_NAME_PATH]: databaseName,
266
+ [POSTGRES_USERNAME_PATH]: username,
267
+ [POSTGRES_CONNECT_URL_PATH]: principalConnectionUrl,
268
+ },
269
+ };
270
+ }
271
+ // CREATE/ALTER USER + CREATE DATABASE — idempotent upsert. The
272
+ // admin-client reapplies privileges every time so the call is
273
+ // safe to repeat.
274
+ const { connectionUrl } = await adminClient.upsertApplicationDatabase({
275
+ databaseName,
276
+ username,
277
+ password,
278
+ });
279
+ return {
280
+ secretBlobValues: {
281
+ [POSTGRES_DATABASE_NAME_PATH]: databaseName,
282
+ [POSTGRES_USERNAME_PATH]: username,
283
+ [POSTGRES_CONNECT_URL_PATH]: connectionUrl,
284
+ },
285
+ };
286
+ },
287
+ /**
288
+ * Tear down the per-app Postgres database + role. Idempotent: the
289
+ * underlying admin client treats missing DB / missing user as
290
+ * no-ops.
291
+ *
292
+ * **Caller status** (F6 of 2026-05-06 plan review): no caller
293
+ * wires this in Slices 1–4.B.2. Method ships as a forward-compat
294
+ * seam for whichever future slice designs the app-deletion
295
+ * lifecycle.
296
+ *
297
+ * **Defensive validation** (Slice 4.A.2 / L4 pattern): empty
298
+ * `appSlug` would derive an empty database name; we fail-loud
299
+ * instead so a bad caller surfaces immediately.
300
+ */
301
+ async revokeAppCredentials(context) {
302
+ if (!context.appSlug) {
303
+ throw new Error(`PostgresSelfInfraProviderModule.revokeAppCredentials: ` +
304
+ `appSlug is empty. ` +
305
+ `The orchestrator MUST supply a non-empty slug.`);
306
+ }
307
+ const adminClient = context.adminClient;
308
+ const databaseName = context.applicationConfigInputs?.[POSTGRES_DATABASE_NAME_INPUT_KEY]
309
+ || buildDatabaseName(context.appSlug);
310
+ const username = buildPostgresPrincipalUsername(context.appSlug, context.principal);
311
+ if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {
312
+ // A named principal owns NOTHING but its own role. Routing it through
313
+ // `dropApplicationDatabase` — which is what this method did before named
314
+ // principals existed — would DROP THE APPLICATION'S DATABASE while
315
+ // believing it was retiring one minion's access.
316
+ await adminClient.dropTableScopedDatabaseRole({
317
+ databaseName,
318
+ username,
319
+ schemaName: POSTGRES_APPLICATION_SCHEMA,
320
+ });
321
+ return;
322
+ }
323
+ await adminClient.dropApplicationDatabase({ databaseName, username });
324
+ },
325
+ // No `mintPlatformServiceCredentials` (D-4 of Slice 4.B —
326
+ // audience-2 not applicable to Postgres_Self today).
327
+ };
328
+ //# sourceMappingURL=postgres-self.infra.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-self.infra.module.js","sourceRoot":"","sources":["../../../../src/postgres-self/postgres-self.infra.module.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `PostgresSelfInfraProviderModule` — third concrete `InfraProviderModule`\n * (Slice 4.B.2 of `infra-provider-planner.md`, generalizing the\n * Slice 2 / 4.B.1 pattern).\n *\n * Implements the `POSTGRES_SELF` `InfraProvider` over the\n * `IPostgresAdminClient` transport (raw `pg` Pool against the\n * Postgres admin connection, exposed by\n * `PostgreSQLConnectionManagerService` after the Slice 4.B.2 5C\n * policy/transport split). Owns ALL per-app naming policy — the\n * per-app database-name + role-name conventions (`wildo_<sanitized>_user`,\n * `wildo_<sanitized>` mirroring the legacy\n * `PlatformInfrastructureManagerService.provisionPostgreSQLDatabase`).\n *\n * **What this module reads from `AppCredentialMintingContext`**:\n * - `appSlug` — used as the source for the sanitized database +\n * user name. Mirrors the legacy convention at\n * `platform-infrastructure-manager.service.ts:300-302`\n * (`sanitizedName = sanitizeForPostgreSQL(applicationName)`,\n * `databaseName = wildo_${sanitizedName}`,\n * `username = wildo_${sanitizedName}_user`).\n * - `secretInputs['database.postgresql.password']` — orchestrator-\n * supplied password (module is upsert-only, never generator).\n * - `applicationConfigInputs?.['databaseName']` — optional override\n * for the derived database name. Mirrors the legacy\n * `provisionPostgreSQLDatabase(appSlug, { databaseName? })` arg.\n * - `adminClient` — narrowed to `IPostgresAdminClient`.\n *\n * **What this module IGNORES from the context** (Slice 4.A.2 / M4\n * forward-compat documentation):\n * - `applicationNamespace` — Postgres per-app DB names use the\n * slug-derived sanitized form (env-suffix isolation comes from\n * the surrounding env-scoped admin connection). Future modules\n * may switch to namespace-scoped naming if cross-env collision\n * risk surfaces.\n * - `wildoApplicationId` — Postgres role names embed the\n * sanitized slug, NOT the application id. This is the legacy\n * convention; preserved for back-compat with apps already\n * provisioned.\n *\n * **Two audiences this module serves:**\n *\n * 1. **Audience-1 (platform-admin, declared via `plan().platformAdmin`)**:\n * cluster-wide Postgres admin creds. Consumers: `apps-manager`\n * (mints per-app DB+role via `mintAppCredentials`), `companion`\n * (mirrors apps-manager admin role for local dev).\n *\n * 2. **Audience-3 (app-scoped, via `plan().appRuntime` +\n * `mintAppCredentials`)**: per-customer-app Postgres database +\n * role. Result threaded into\n * `Wildo_Application_Secrets.database.postgresql.{\n * databaseName, username, connectUrl}` via\n * `_merge_WildoApplicationSecrets`. Note: the password is\n * embedded in `connectUrl` — there is no separate\n * `database.postgresql.password` blob path today (matches the\n * legacy persistence shape at\n * `wildo-application-lifecycle.service.ts:2191-2200`).\n *\n * **NOT served (D-4 of Slice 4.B locked decisions, 2026-05-10)**:\n * - **Audience-2 (platform-service-scoped)**: no platform service\n * uses Postgres for inter-service messaging today. Method omitted.\n *\n * **`revokeAppCredentials`**: declared and tested, but the integration\n * call site does NOT exist in `wildo-application-lifecycle.service.ts`\n * (no app-deletion ceremony today — F6 of plan review 2026-05-06).\n * Method ships as a forward-compat seam.\n */\n\nimport {\n AppCredentialMintingContext,\n AppCredentialMintingResult,\n IPostgresAdminClient,\n InfraCapability,\n InfraPlatformAdminConsumer,\n InfraProvider,\n InfraProviderModule,\n InfraProviderPlanContribution,\n ApplicationRuntimePrincipal,\n ApplicationRuntimePrincipalKind,\n resolveGrantStorageNames,\n} from '@wildo-ai/platform-config-lib';\n\n/**\n * Default Postgres port (matches `INFRA_DEFAULT_PORTS.POSTGRES` in\n * the shared platform constants — duplicated as a literal because\n * the planner output is consumed by the CLI which materializes it\n * into `Wildo_Application_Config` blob paths).\n */\nconst DEFAULT_POSTGRES_PORT = 5432;\n\n/**\n * The `Wildo_Application_Secrets` JSON paths this module writes\n * through `mintAppCredentials`. Declared once and reused as\n * `appRuntime.perAppSecretBlobPaths` in `plan()` AND as the keys of\n * `secretBlobValues` in the result, so a refactor that renames a\n * path fails loudly at one place.\n *\n * Note: the password is embedded in `connectUrl` (Postgres\n * convention); there is no separate `database.postgresql.password`\n * blob path today. The orchestrator persists `connectUrl`\n * verbatim into `Wildo_Application_Secrets`.\n */\nconst POSTGRES_DATABASE_NAME_PATH = 'database.postgresql.databaseName';\nconst POSTGRES_USERNAME_PATH = 'database.postgresql.username';\nconst POSTGRES_CONNECT_URL_PATH = 'database.postgresql.connectUrl';\n/**\n * The paths this module mints PER RUNTIME PRINCIPAL — the single source for both consumers.\n *\n * Read by `InfraProviderModule.perPrincipalSecretBlobPaths` (statically, so apps-manager can strip\n * these from the application blob before delivering a principal's own material) AND spread into\n * `appRuntime.perAppSecretBlobPaths` inside `plan()`. Declaring the list twice is how the delivery\n * path would silently stop covering a path this module still mints.\n */\nconst PER_PRINCIPAL_SECRET_BLOB_PATHS: ReadonlyArray<string> = [\n POSTGRES_DATABASE_NAME_PATH,\n POSTGRES_USERNAME_PATH,\n POSTGRES_CONNECT_URL_PATH,\n];\n\n\n/**\n * Key the module looks for in `secretInputs` to read the\n * orchestrator-supplied password.\n */\nconst POSTGRES_PASSWORD_INPUT_KEY = 'database.postgresql.password';\n\n/**\n * Optional key the module looks for in `applicationConfigInputs`\n * for an authored database-name override. When omitted, the module\n * derives the database name from `appSlug` via `sanitizeForPostgreSQL`.\n */\nconst POSTGRES_DATABASE_NAME_INPUT_KEY = 'databaseName';\n\n/**\n * Sanitize an application slug for Postgres identifier rules.\n * Mirrors the legacy `sanitizeForPostgreSQL` at\n * `platform-infrastructure-manager.service.ts:371-378`:\n * - lowercase\n * - `[^a-z0-9_]` → `_`\n * - collapse multiple `_`\n * - strip leading/trailing `_`\n * - cap at 50 chars (Postgres identifier limit is 63; leaves room\n * for the `wildo_` prefix and `_user` suffix).\n *\n * Centralized here so the module owns the convention; if the cap\n * or escape rules change, one site changes.\n */\nfunction sanitizeForPostgres(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9_]/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '')\n .substring(0, 50);\n}\n\n/**\n * Compute the per-app database name. Mirrors legacy convention at\n * `platform-infrastructure-manager.service.ts:301`:\n * `wildo_<sanitized>`\n */\nfunction buildDatabaseName(appSlug: string): string {\n return `wildo_${sanitizeForPostgres(appSlug)}`;\n}\n\n/**\n * Compute the per-app database role. Mirrors legacy convention at\n * `platform-infrastructure-manager.service.ts:302`:\n * `wildo_<sanitized>_user`\n */\nfunction buildUsername(appSlug: string): string {\n return `wildo_${sanitizeForPostgres(appSlug)}_user`;\n}\n\n/**\n * The schema a per-app database's resource tables live in.\n *\n * `public` is what the schema planner targets today. Named as a constant rather\n * than inlined at the four sites that need it because a grant issued into one\n * schema and revoked from another is a revocation that silently succeeds while\n * narrowing nothing.\n */\nconst POSTGRES_APPLICATION_SCHEMA = 'public';\n\n/**\n * Per-principal Postgres role name — the SAME derivation the revoke path uses.\n *\n * Fourth time this shape appears (Redis, RabbitMQ, Mongo, now Postgres), and the\n * consequence is worse here than anywhere else: a revoke that derived the name\n * differently would miss the minion's role AND, on the pre-existing\n * `dropApplicationDatabase` path, take the application's entire database with\n * it. The application backend keeps `wildo_<slug>_user` VERBATIM so an existing\n * deployment's owner role is never orphaned.\n */\nfunction buildPostgresPrincipalUsername(\n appSlug: string,\n principal: ApplicationRuntimePrincipal | undefined,\n): string {\n if (!principal || principal.kind === ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n return buildUsername(appSlug);\n }\n return `wildo_${sanitizeForPostgres(appSlug)}_${sanitizeForPostgres(principal.kind)}_`\n + `${sanitizeForPostgres(principal.name ?? '')}_user`;\n}\n\nexport const PostgresSelfInfraProviderModule: InfraProviderModule = {\n provider: InfraProvider.POSTGRES_SELF,\n supportedCapabilities: [InfraCapability.RELATIONAL_DATABASE],\n perPrincipalSecretBlobPaths: PER_PRINCIPAL_SECRET_BLOB_PATHS,\n\n plan(): InfraProviderPlanContribution {\n return {\n dockerComposeServices: ['postgres'],\n k8sResources: ['templates/infra/k8s/internal/postgres/deployment.yaml.hbs'],\n platformAdmin: {\n envVars: {\n PG_HOST: 'postgres',\n PG_PORT: String(DEFAULT_POSTGRES_PORT),\n },\n // Admin user + password to provision per-app DBs/roles. The\n // legacy admin-init code reads these from `PG_ADMIN_USER` /\n // `PG_ADMIN_PASSWORD`; the planner emits the keys, the\n // CLI's `env-vars-builder` writes the values at sync time.\n secretEnvVars: ['PG_ADMIN_USER', 'PG_ADMIN_PASSWORD'],\n // Audience-1 only (DB-admin access). No audience-2\n // platform-service-scoped Postgres use case today (D-4).\n consumers: [\n InfraPlatformAdminConsumer.APPS_MANAGER,\n InfraPlatformAdminConsumer.COMPANION,\n ],\n },\n appRuntime: {\n // Static topology — same for every app in this env. The\n // per-app values (databaseName, username, connectUrl) flow\n // through `perAppSecretBlobPaths`.\n perAppConfigBlobPaths: {\n 'database.postgresql.host': 'postgres',\n 'database.postgresql.port': DEFAULT_POSTGRES_PORT,\n },\n perAppSecretBlobPaths: [...PER_PRINCIPAL_SECRET_BLOB_PATHS],\n },\n };\n },\n\n async mintAppCredentials(\n context: AppCredentialMintingContext,\n ): Promise<AppCredentialMintingResult> {\n const adminClient = context.adminClient as IPostgresAdminClient;\n const password = context.secretInputs[POSTGRES_PASSWORD_INPUT_KEY];\n if (!password) {\n throw new Error(\n `PostgresSelfInfraProviderModule.mintAppCredentials: ` +\n `secretInputs is missing '${POSTGRES_PASSWORD_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the Postgres password — modules are upsert-only.`,\n );\n }\n if (!context.appSlug) {\n throw new Error(\n `PostgresSelfInfraProviderModule.mintAppCredentials: ` +\n `appSlug is empty. ` +\n `The orchestrator MUST supply a non-empty slug.`,\n );\n }\n\n // Allow operator override of the derived database name. Without\n // an override, derive from `appSlug` via the legacy convention.\n const databaseName =\n context.applicationConfigInputs?.[POSTGRES_DATABASE_NAME_INPUT_KEY]\n || buildDatabaseName(context.appSlug);\n const username = buildPostgresPrincipalUsername(context.appSlug, context.principal);\n\n if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n // A named principal gets a TABLE-SCOPED role (Step 3 / R1 / D14): exactly\n // the verbs its `resourceAccess` derived, on exactly the tables that\n // declaration names, inside a database it does not own.\n //\n // `resourceGrants` is REQUIRED and its absence is an error rather than a\n // default. Falling through to `upsertApplicationDatabase` would make the\n // minion the OWNER of the application's database with `ALL PRIVILEGES` —\n // the exact over-grant this path replaces, arriving silently through a\n // code path that reads as if it were doing the narrow thing.\n const grants = context.resourceGrants;\n if (!grants) {\n throw new Error(\n `PostgresSelfInfraProviderModule.mintAppCredentials: `\n + `principal '${context.principal.kind}:${context.principal.name}' has no resourceGrants. `\n + `Refusing to fall back to the database-wide application role — a minion with no declared `\n + `access must get NO database role at all (D11/R3), which is the orchestrator's decision to make.`,\n );\n }\n const { connectionUrl: principalConnectionUrl } = await adminClient.upsertTableScopedDatabaseRole({\n databaseName,\n username,\n password,\n schemaName: POSTGRES_APPLICATION_SCHEMA,\n // Resource types → table names HERE, at the adapter that speaks\n // Postgres. The grant contract itself stays in the platform vocabulary,\n // and passes through the SAME translation Mongo uses, so the two\n // adapters cannot disagree about which rows a resource type addresses.\n readTables: resolveGrantStorageNames(grants.read),\n writeTables: resolveGrantStorageNames(grants.write),\n });\n\n return {\n secretBlobValues: {\n [POSTGRES_DATABASE_NAME_PATH]: databaseName,\n [POSTGRES_USERNAME_PATH]: username,\n [POSTGRES_CONNECT_URL_PATH]: principalConnectionUrl,\n },\n };\n }\n\n // CREATE/ALTER USER + CREATE DATABASE — idempotent upsert. The\n // admin-client reapplies privileges every time so the call is\n // safe to repeat.\n const { connectionUrl } = await adminClient.upsertApplicationDatabase({\n databaseName,\n username,\n password,\n });\n\n return {\n secretBlobValues: {\n [POSTGRES_DATABASE_NAME_PATH]: databaseName,\n [POSTGRES_USERNAME_PATH]: username,\n [POSTGRES_CONNECT_URL_PATH]: connectionUrl,\n },\n };\n },\n\n /**\n * Tear down the per-app Postgres database + role. Idempotent: the\n * underlying admin client treats missing DB / missing user as\n * no-ops.\n *\n * **Caller status** (F6 of 2026-05-06 plan review): no caller\n * wires this in Slices 1–4.B.2. Method ships as a forward-compat\n * seam for whichever future slice designs the app-deletion\n * lifecycle.\n *\n * **Defensive validation** (Slice 4.A.2 / L4 pattern): empty\n * `appSlug` would derive an empty database name; we fail-loud\n * instead so a bad caller surfaces immediately.\n */\n async revokeAppCredentials(context: AppCredentialMintingContext): Promise<void> {\n if (!context.appSlug) {\n throw new Error(\n `PostgresSelfInfraProviderModule.revokeAppCredentials: ` +\n `appSlug is empty. ` +\n `The orchestrator MUST supply a non-empty slug.`,\n );\n }\n const adminClient = context.adminClient as IPostgresAdminClient;\n const databaseName =\n context.applicationConfigInputs?.[POSTGRES_DATABASE_NAME_INPUT_KEY]\n || buildDatabaseName(context.appSlug);\n const username = buildPostgresPrincipalUsername(context.appSlug, context.principal);\n\n if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n // A named principal owns NOTHING but its own role. Routing it through\n // `dropApplicationDatabase` — which is what this method did before named\n // principals existed — would DROP THE APPLICATION'S DATABASE while\n // believing it was retiring one minion's access.\n await adminClient.dropTableScopedDatabaseRole({\n databaseName,\n username,\n schemaName: POSTGRES_APPLICATION_SCHEMA,\n });\n return;\n }\n\n await adminClient.dropApplicationDatabase({ databaseName, username });\n },\n\n // No `mintPlatformServiceCredentials` (D-4 of Slice 4.B —\n // audience-2 not applicable to Postgres_Self today).\n};\n"]}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `RabbitmqSelfInfraProviderModule` — first concrete `InfraProviderModule`
3
+ * (Slices 1–2.5 of `infra-provider-planner.md`).
4
+ *
5
+ * Implements the `RABBITMQ_SELF` `InfraProvider` over the
6
+ * `IRabbitMQManagementClient` transport (raw HTTP `PUT /api/users` etc.,
7
+ * exposed by `RabbitMQConnectionManagerService` after the 5C policy/
8
+ * transport split). Owns ALL per-app and per-platform-service
9
+ * permission policy — the regex shapes, the broader-permission
10
+ * special case for `crontabs-batches-manager`, the per-app username
11
+ * convention, the optional vhost activation.
12
+ *
13
+ * **What this module reads from `AppCredentialMintingContext`**:
14
+ * - `applicationNamespace` — used as the rabbit username
15
+ * (`<appSlug>-<envId>`, the legacy convention from
16
+ * `wildo-application-lifecycle.service.ts`).
17
+ * - `wildoApplicationId` — used as the permission-regex disambiguator
18
+ * (matches `^wildo\.(queue|reply)\.<id>\..*`).
19
+ * - `secretInputs['database.rabbitmq.password']` — orchestrator-
20
+ * supplied password (module is upsert-only, never generator).
21
+ * - `applicationConfigInputs?.['vhost']` — optional per-app vhost
22
+ * for true vhost-level isolation (Slice 2.5c-3).
23
+ * - `adminClient` — narrowed to `IRabbitMQManagementClient`.
24
+ *
25
+ * **What this module IGNORES from the context** (Slice 4.A.2 / M4):
26
+ * - `appSlug` — RabbitMQ resources are namespace-scoped, not slug-
27
+ * scoped. Future modules (MongoDB DB name, etc.) WILL use `appSlug`.
28
+ * This is documented forward-compat for module authors: not every
29
+ * module reads every context field; each declares which it consumes.
30
+ *
31
+ * **Three audiences this module serves:**
32
+ *
33
+ * 1. **Audience-1 (platform-admin, declared via `plan().platformAdmin`)**:
34
+ * cluster-wide management-API creds (`RABBITMQ_ADMIN_USERNAME` /
35
+ * `RABBITMQ_ADMIN_PASSWORD`). Consumers: `apps-manager`,
36
+ * `companion`. Delivered through process.env (the CLI's
37
+ * `env-vars-builder` writes them per-env). No method.
38
+ *
39
+ * 2. **Audience-2 (platform-service-scoped, via
40
+ * `mintPlatformServiceCredentials`)**: per-platform-service broker
41
+ * users for inter-service messaging (`apps-manager`,
42
+ * `crontabs-batches-manager`, `file-generator`). Username =
43
+ * `serviceName`; permission regex is service-namespaced
44
+ * (`^wildo\.platform\.<slug>\..*`); crontabs gets the broader
45
+ * `^wildo\.(queue|reply|platform)\..*` per the legacy override.
46
+ *
47
+ * **Slice 2.5 behavior-parity note**: the legacy code paths
48
+ * `provisionServiceCredentials` and `ensureAppsManagerCredentials`
49
+ * produced slightly different topic-permission regexes for the
50
+ * same apps-manager user (`wildo\.platform\.apps_manager\..*` vs
51
+ * `wildo.platform.apps_manager.*`). User permissions were therefore
52
+ * order-dependent. This module unifies on the dot-escaped variant.
53
+ * Functionally equivalent for real queue names; tighter spec.
54
+ *
55
+ * **Slice 4.A.2 / M2 design note**: the legacy
56
+ * `getDefaultRabbitMQPermissions.read` field declared a BROADER
57
+ * regex than `configure`/`write` (included
58
+ * `wildo.platform.broadcast.*`) — but that widening NEVER reached
59
+ * the broker (the legacy `upsertTenantUserPermissions` ignored
60
+ * the `read` arg and used `resourceNameRegex` for both
61
+ * `configure` and `read`). This module preserves that behavior:
62
+ * `read === configure`. If platform services need a broadcast
63
+ * namespace in the future, extend `mintPlatformServiceCredentials`
64
+ * with an explicit broadcast-read regex.
65
+ *
66
+ * 3. **Audience-3 (app-scoped, via `plan().appRuntime` +
67
+ * `mintAppCredentials`)**: per-customer-app broker user. Username
68
+ * = `applicationNamespace`; permission regex disambiguates by
69
+ * `wildoApplicationId` (Mongo ObjectId — globally unique). Result
70
+ * threaded into `Wildo_Application_Secrets.database.rabbitmq.{
71
+ * username, password}` via `_merge_WildoApplicationSecrets`.
72
+ *
73
+ * **Vhost activation (Slice 2.5c-3, opt-in)**: when the operator
74
+ * authors `backingServices.rabbitmq.vhost` in
75
+ * `wildo.infra.<envName>.config.ts`, the CLI emits `RABBITMQ_VHOST`
76
+ * into apps-manager's process.env; the lifecycle service threads it
77
+ * via `applicationConfigInputs.vhost`; this module
78
+ * `ensureVhost`-then-permissions-on-it. Without the field, the
79
+ * module falls back to `/` (legacy permission-regex isolation).
80
+ *
81
+ * **`revokeAppCredentials`**: declared and tested, but the integration
82
+ * call site does NOT exist in `wildo-application-lifecycle.service.ts`
83
+ * (no app-deletion ceremony today — F6 of plan review 2026-05-06).
84
+ * Method ships as a forward-compat seam.
85
+ */
86
+ import { InfraProviderModule } from '@wildo-ai/platform-config-lib';
87
+ export declare const RabbitmqSelfInfraProviderModule: InfraProviderModule;
88
+ //# sourceMappingURL=rabbitmq-self.infra.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rabbitmq-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/rabbitmq-self/rabbitmq-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAEH,OAAO,EAOL,mBAAmB,EAOpB,MAAM,+BAA+B,CAAC;AAwMvC,eAAO,MAAM,+BAA+B,EAAE,mBAsO7C,CAAC"}