@wildo-ai/platform-infra-providers 1.1.3 → 1.1.5
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.
- package/dist/esm/minio-self/minio-self.infra.module.d.ts.map +1 -1
- package/dist/esm/minio-self/minio-self.infra.module.js +10 -8
- package/dist/esm/minio-self/minio-self.infra.module.js.map +1 -1
- package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts +3 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts.map +1 -1
- package/dist/esm/mongodb-self/mongodb-self.infra.module.js +62 -9
- package/dist/esm/mongodb-self/mongodb-self.infra.module.js.map +1 -1
- package/dist/esm/postgres-self/postgres-self.infra.module.d.ts +3 -4
- package/dist/esm/postgres-self/postgres-self.infra.module.d.ts.map +1 -1
- package/dist/esm/postgres-self/postgres-self.infra.module.js +23 -15
- package/dist/esm/postgres-self/postgres-self.infra.module.js.map +1 -1
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts +6 -7
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts.map +1 -1
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js +9 -12
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js.map +1 -1
- package/dist/esm/redis-self/redis-self.infra.module.d.ts +3 -4
- package/dist/esm/redis-self/redis-self.infra.module.d.ts.map +1 -1
- package/dist/esm/redis-self/redis-self.infra.module.js +6 -10
- package/dist/esm/redis-self/redis-self.infra.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/minio-self/minio-self.infra.module.ts +10 -8
- package/src/mongodb-self/__tests__/mongodb-self.infra.module.test.ts +70 -5
- package/src/mongodb-self/mongodb-self.infra.module.ts +66 -8
- package/src/postgres-self/__tests__/postgres-self.infra.module.test.ts +8 -2
- package/src/postgres-self/postgres-self.infra.module.ts +24 -14
- package/src/rabbitmq-self/rabbitmq-self.infra.module.ts +9 -12
- package/src/redis-self/redis-self.infra.module.ts +6 -10
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts +0 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts.map +0 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js +0 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js.map +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minio-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/minio-self/minio-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,EAIL,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AAIvC,eAAO,MAAM,4BAA4B,EAAE,
|
|
1
|
+
{"version":3,"file":"minio-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/minio-self/minio-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,EAIL,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AAIvC,eAAO,MAAM,4BAA4B,EAAE,mBA6C1C,CAAC"}
|
|
@@ -91,15 +91,17 @@ export const MinioSelfInfraProviderModule = {
|
|
|
91
91
|
},
|
|
92
92
|
// Apps receive `STORAGE_*` via the bootstrap env contract
|
|
93
93
|
// (engine-side `application-backend-env.contracts.ts`). No
|
|
94
|
-
// per-app blob
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
94
|
+
// per-app blob paths: storage holds no per-app SECRET (sessions are
|
|
95
|
+
// STS-issued by apps-manager). The BUCKET is per application — each
|
|
96
|
+
// authors `backingServices.s3.bucket`, sent at registration and
|
|
97
|
+
// recorded by apps-manager (#1107) — but it is addressing, not a
|
|
98
|
+
// secret, so `appRuntime` stays empty.
|
|
98
99
|
};
|
|
99
100
|
},
|
|
100
|
-
// No `mintAppCredentials` / `revokeAppCredentials` —
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
101
|
+
// No `mintAppCredentials` / `revokeAppCredentials` — application
|
|
102
|
+
// storage access is an STS session apps-manager issues per request,
|
|
103
|
+
// confined to that application's recorded bucket (#1107).
|
|
104
|
+
// No `mintPlatformServiceCredentials` — platform services use
|
|
105
|
+
// apps-manager's own bucket through the same broker.
|
|
104
106
|
};
|
|
105
107
|
//# sourceMappingURL=minio-self.infra.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minio-self.infra.module.js","sourceRoot":"","sources":["../../../../src/minio-self/minio-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,aAAa,GAGd,MAAM,+BAA+B,CAAC;AAEvC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,CAAC,MAAM,4BAA4B,GAAwB;IAC/D,QAAQ,EAAE,aAAa,CAAC,UAAU;IAClC,qBAAqB,EAAE,CAAC,eAAe,CAAC,cAAc,CAAC;IAEvD;;;OAGG;IACH,2BAA2B,EAAE,EAAE;IAE/B,IAAI;QACF,OAAO;YACL,qBAAqB,EAAE,CAAC,OAAO,CAAC;YAChC,YAAY,EAAE,CAAC,wDAAwD,CAAC;YACxE,aAAa,EAAE;gBACb,OAAO,EAAE;oBACP,gBAAgB,EAAE,OAAO;oBACzB,gBAAgB,EAAE,OAAO;oBACzB,YAAY,EAAE,MAAM,CAAC,kBAAkB,CAAC;oBACxC,eAAe,EAAE,OAAO;iBACzB;gBACD,4DAA4D;gBAC5D,yDAAyD;gBACzD,sBAAsB;gBACtB,aAAa,EAAE,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;gBAC3D,SAAS,EAAE;oBACT,0BAA0B,CAAC,YAAY;oBACvC,0BAA0B,CAAC,SAAS;iBACrC;aACF;YACD,0DAA0D;YAC1D,2DAA2D;YAC3D,
|
|
1
|
+
{"version":3,"file":"minio-self.infra.module.js","sourceRoot":"","sources":["../../../../src/minio-self/minio-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,aAAa,GAGd,MAAM,+BAA+B,CAAC;AAEvC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,CAAC,MAAM,4BAA4B,GAAwB;IAC/D,QAAQ,EAAE,aAAa,CAAC,UAAU;IAClC,qBAAqB,EAAE,CAAC,eAAe,CAAC,cAAc,CAAC;IAEvD;;;OAGG;IACH,2BAA2B,EAAE,EAAE;IAE/B,IAAI;QACF,OAAO;YACL,qBAAqB,EAAE,CAAC,OAAO,CAAC;YAChC,YAAY,EAAE,CAAC,wDAAwD,CAAC;YACxE,aAAa,EAAE;gBACb,OAAO,EAAE;oBACP,gBAAgB,EAAE,OAAO;oBACzB,gBAAgB,EAAE,OAAO;oBACzB,YAAY,EAAE,MAAM,CAAC,kBAAkB,CAAC;oBACxC,eAAe,EAAE,OAAO;iBACzB;gBACD,4DAA4D;gBAC5D,yDAAyD;gBACzD,sBAAsB;gBACtB,aAAa,EAAE,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;gBAC3D,SAAS,EAAE;oBACT,0BAA0B,CAAC,YAAY;oBACvC,0BAA0B,CAAC,SAAS;iBACrC;aACF;YACD,0DAA0D;YAC1D,2DAA2D;YAC3D,oEAAoE;YACpE,oEAAoE;YACpE,gEAAgE;YAChE,iEAAiE;YACjE,uCAAuC;SACxC,CAAC;IACJ,CAAC;IAED,iEAAiE;IACjE,oEAAoE;IACpE,0DAA0D;IAC1D,8DAA8D;IAC9D,qDAAqD;CACtD,CAAC","sourcesContent":["/**\n * `MinioSelfInfraProviderModule` — sixth concrete `InfraProviderModule`\n * (Slice 5.1 of `infra-provider-planner.md`, generalizing the\n * Slice 2 / 4.B pattern to object storage).\n *\n * Implements the `MINIO_SELF` `InfraProvider`. One shared MinIO bucket\n * serves every application; the object-key tree is namespaced\n * `${prefix}/applications/${applicationId}/files/${fileId}`.\n *\n * `mintAppCredentials` / `revokeAppCredentials` are absent from THIS\n * module, and that remains accurate — but it no longer means what it\n * used to. Per-application credential isolation exists; it is simply\n * not implemented here. See below.\n *\n * ## ⚠️ Corrected 2026-08-10 — this block described a posture the\n * system no longer has\n *\n * It previously stated that every app shares \"one access-key/secret-key\n * pair\", that per-app isolation \"comes from `STORAGE_BUCKET_PREFIX`\",\n * and that app-scoped credentials were \"NONE — apps consume the shared\n * `STORAGE_*` env\". All three were true when written and are now false:\n *\n * - Applications no longer receive any long-lived storage credential.\n * `STORAGE_ACCESS_KEY` / `STORAGE_SECRET_KEY` are no longer emitted\n * into application-tier env at all — they were the object store's\n * ROOT pair, and handing them to every app, minion and worker is the\n * exposure this was corrected to remove.\n * - Isolation is enforced by a **credential**, not by a prefix\n * convention. `apps-manager`'s `ObjectStorageStsBrokerService` issues\n * short-lived STS sessions whose inline policy confines each\n * application to `applications/<applicationId>/`, keyed on the\n * platform-minted id — never on `STORAGE_BUCKET_PREFIX`, which an\n * application authors for itself and therefore cannot be a security\n * boundary. The prefix is now cosmetic namespacing.\n *\n * The three prerequisites this block listed for per-app credentials\n * were addressed rather than deferred: the adapter threads per-principal\n * credentials through a credential provider (1); the session policy\n * replaces per-app bucket-policy authoring (2); and rotation safety (3)\n * dissolved — STS credentials expire by construction, so there is no\n * long-lived key whose rotation must be coordinated across pods.\n *\n * **Why the mint methods still do not live here.** MinIO's JS client\n * ships no user-management API, so minting would need the `mc` binary or\n * hand-written admin-API crypto. STS needs neither, and belongs with the\n * service that holds the admin credential — apps-manager — rather than\n * with this planning module, which contributes deployment topology.\n *\n * **Audience-1 (platform-admin)**: cluster-wide MinIO admin creds\n * delivered via env. Consumers: `apps-manager` (which brokers STS from\n * them) and `companion`. Whether these consumers should hold *root*\n * specifically, rather than a narrower admin credential, is OQ-2 in the\n * plan below.\n *\n * **Audience-3 (app-scoped)**: no per-app MinIO STATE is minted here —\n * but applications DO receive a per-application storage credential at\n * runtime, from apps-manager. \"No app-scoped audience in this module\" is\n * not \"no app-scoped credential in the system\".\n *\n * See `.claude/plans/object-storage-root-credential-distribution.md`.\n */\n\nimport {\n InfraCapability,\n InfraPlatformAdminConsumer,\n InfraProvider,\n InfraProviderModule,\n InfraProviderPlanContribution,\n} from '@wildo-ai/platform-config-lib';\n\nconst DEFAULT_MINIO_PORT = 9000;\n\nexport const MinioSelfInfraProviderModule: InfraProviderModule = {\n provider: InfraProvider.MINIO_SELF,\n supportedCapabilities: [InfraCapability.OBJECT_STORAGE],\n\n /**\n * Shared bucket, shared root key — same as AWS S3: object-storage confinement is an STS session\n * policy minted per request, never a per-principal secret in the application blob.\n */\n perPrincipalSecretBlobPaths: [],\n\n plan(): InfraProviderPlanContribution {\n return {\n dockerComposeServices: ['minio'],\n k8sResources: ['templates/infra/k8s/internal/minio/deployment.yaml.hbs'],\n platformAdmin: {\n envVars: {\n STORAGE_PROVIDER: 'minio',\n STORAGE_ENDPOINT: 'minio',\n STORAGE_PORT: String(DEFAULT_MINIO_PORT),\n STORAGE_USE_SSL: 'false',\n },\n // Cluster-wide bucket access creds. Every app uses the same\n // pair. Per-app isolation is via `STORAGE_BUCKET_PREFIX`\n // (see module JSDoc).\n secretEnvVars: ['STORAGE_ACCESS_KEY', 'STORAGE_SECRET_KEY'],\n consumers: [\n InfraPlatformAdminConsumer.APPS_MANAGER,\n InfraPlatformAdminConsumer.COMPANION,\n ],\n },\n // Apps receive `STORAGE_*` via the bootstrap env contract\n // (engine-side `application-backend-env.contracts.ts`). No\n // per-app blob paths: storage holds no per-app SECRET (sessions are\n // STS-issued by apps-manager). The BUCKET is per application — each\n // authors `backingServices.s3.bucket`, sent at registration and\n // recorded by apps-manager (#1107) — but it is addressing, not a\n // secret, so `appRuntime` stays empty.\n };\n },\n\n // No `mintAppCredentials` / `revokeAppCredentials` — application\n // storage access is an STS session apps-manager issues per request,\n // confined to that application's recorded bucket (#1107).\n // No `mintPlatformServiceCredentials` — platform services use\n // apps-manager's own bucket through the same broker.\n};\n"]}
|
|
@@ -76,6 +76,9 @@ export declare function buildMinionMongodbPrivileges(input: {
|
|
|
76
76
|
databaseName: string;
|
|
77
77
|
readCollections: readonly string[];
|
|
78
78
|
writeCollections: readonly string[];
|
|
79
|
+
/** Insert-only — see {@link MONGODB_APPEND_ACTIONS}. Optional so existing pins read unchanged. */
|
|
80
|
+
appendCollections?: readonly string[];
|
|
81
|
+
maintainCollections?: readonly string[];
|
|
79
82
|
}): MongodbCollectionPrivilege[];
|
|
80
83
|
export declare const MongodbSelfInfraProviderModule: InfraProviderModule;
|
|
81
84
|
//# sourceMappingURL=mongodb-self.infra.module.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongodb-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/mongodb-self/mongodb-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAOL,mBAAmB,
|
|
1
|
+
{"version":3,"file":"mongodb-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/mongodb-self/mongodb-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAOL,mBAAmB,EAMpB,MAAM,+BAA+B,CAAC;AA2FvC,qDAAqD;AACrD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kGAAkG;IAClG,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,GAAG,0BAA0B,EAAE,CAe/B;AA+CD,eAAO,MAAM,8BAA8B,EAAE,mBAoM5C,CAAC"}
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*
|
|
41
41
|
* **NOT served (D-4)**: audience-2 not applicable.
|
|
42
42
|
*/
|
|
43
|
-
import { InfraCapability, InfraPlatformAdminConsumer, InfraProvider, ApplicationRuntimePrincipalKind, resolveGrantStorageNames, } from '@wildo-ai/platform-config-lib';
|
|
43
|
+
import { InfraCapability, InfraPlatformAdminConsumer, InfraProvider, ApplicationRuntimePrincipalKind, deriveMinionDatabaseGrants, resolveGrantStorageNames, } from '@wildo-ai/platform-config-lib';
|
|
44
44
|
const MONGODB_USERNAME_PATH = 'database.mongodb.username';
|
|
45
45
|
const MONGODB_PASSWORD_PATH = 'database.mongodb.password';
|
|
46
46
|
/**
|
|
@@ -83,6 +83,46 @@ function buildMongodbUsername(applicationNamespace) {
|
|
|
83
83
|
*/
|
|
84
84
|
const MONGODB_READ_ACTIONS = ['find'];
|
|
85
85
|
const MONGODB_WRITE_ACTIONS = ['insert', 'update', 'remove'];
|
|
86
|
+
/**
|
|
87
|
+
* Append-only: `insert` and `find`, WITHOUT `update` or `remove`. Carries the engine's audit-log
|
|
88
|
+
* floor, where a principal must be able to record what it did and must never be able to unrecord it.
|
|
89
|
+
*
|
|
90
|
+
* `find` is not a widening — see {@link MONGODB_MAINTAIN_ACTIONS}, whose note covers both floors.
|
|
91
|
+
*/
|
|
92
|
+
const MONGODB_APPEND_ACTIONS = ['insert', 'find'];
|
|
93
|
+
/**
|
|
94
|
+
* Durable runtime state may be inserted, read back and updated, never removed.
|
|
95
|
+
*
|
|
96
|
+
* ## Why the two FLOORS carry `find` and the author-declared write axis above does not
|
|
97
|
+
*
|
|
98
|
+
* Without it this axis could not perform the update the engine actually issues. Measured 2026-09-16
|
|
99
|
+
* against the running replica set, as a throwaway role holding exactly these actions:
|
|
100
|
+
*
|
|
101
|
+
* | actions | operation | outcome |
|
|
102
|
+
* |---|---|---|
|
|
103
|
+
* | `insert`, `update` | `updateOne({_id}, …)` | works |
|
|
104
|
+
* | `insert`, `update` | **`findOneAndUpdate`** | **Unauthorized** |
|
|
105
|
+
* | `insert`, `find`, `update` | `findOneAndUpdate` | works; `deleteOne` still Unauthorized |
|
|
106
|
+
*
|
|
107
|
+
* The third row is the one that matters: `RepositoryResourceBackendService` updates through
|
|
108
|
+
* `findOneAndUpdate`, so a role without `find` could not update at all — only a filter-less write no
|
|
109
|
+
* caller issues. Note MongoDB and PostgreSQL differ here, which is why each was measured rather than
|
|
110
|
+
* inferred from the other: Mongo's `update` action covers its own filter, while PostgreSQL requires
|
|
111
|
+
* `SELECT` for the columns a `WHERE` reads.
|
|
112
|
+
*
|
|
113
|
+
* `find` on the APPEND axis was NOT broken on MongoDB — `insertOne` succeeds without it. It is added
|
|
114
|
+
* so an axis means the same thing on both adapters: `deriveMinionDatabaseGrants` exists precisely so
|
|
115
|
+
* the two cannot drift, and an append grant that carried read-back on one adapter and not the other
|
|
116
|
+
* would make the same declaration yield two different authorities.
|
|
117
|
+
*
|
|
118
|
+
* **What these floors withhold is untouched.** Append still holds no `update` and no `remove`;
|
|
119
|
+
* maintain still holds no `remove`. Both were re-probed after adding `find`, and `deleteOne` stayed
|
|
120
|
+
* Unauthorized. The author-declared write axis keeps write-without-read deliberately, because there
|
|
121
|
+
* the author has a remedy — declare the read — which `deriveMinionResourceGrants` argues for at
|
|
122
|
+
* length. These two axes are engine-imposed and refuse any declaration, so no author action could
|
|
123
|
+
* ever have repaired them.
|
|
124
|
+
*/
|
|
125
|
+
const MONGODB_MAINTAIN_ACTIONS = ['insert', 'find', 'update'];
|
|
86
126
|
/**
|
|
87
127
|
* Turn a minion's grants into collection-scoped MongoDB privileges (D14, R1).
|
|
88
128
|
*
|
|
@@ -114,6 +154,12 @@ export function buildMinionMongodbPrivileges(input) {
|
|
|
114
154
|
for (const collection of input.writeCollections) {
|
|
115
155
|
privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_WRITE_ACTIONS] });
|
|
116
156
|
}
|
|
157
|
+
for (const collection of input.appendCollections ?? []) {
|
|
158
|
+
privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_APPEND_ACTIONS] });
|
|
159
|
+
}
|
|
160
|
+
for (const collection of input.maintainCollections ?? []) {
|
|
161
|
+
privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_MAINTAIN_ACTIONS] });
|
|
162
|
+
}
|
|
117
163
|
return privileges;
|
|
118
164
|
}
|
|
119
165
|
/**
|
|
@@ -206,8 +252,8 @@ export const MongodbSelfInfraProviderModule = {
|
|
|
206
252
|
const username = buildMongodbPrincipalUsername(context.applicationNamespace, context.principal);
|
|
207
253
|
if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {
|
|
208
254
|
// A named principal gets a COLLECTION-SCOPED user (Step 3 / R1 / D14):
|
|
209
|
-
// one custom role carrying
|
|
210
|
-
//
|
|
255
|
+
// one custom role carrying authored business grants plus the mandatory
|
|
256
|
+
// runtime floor, and no inherited database-wide privileges.
|
|
211
257
|
//
|
|
212
258
|
// `resourceGrants` is REQUIRED here and its absence is an error rather
|
|
213
259
|
// than a default. Falling back to the app-wide upsert would hand a minion
|
|
@@ -218,9 +264,10 @@ export const MongodbSelfInfraProviderModule = {
|
|
|
218
264
|
if (!grants) {
|
|
219
265
|
throw new Error(`MongodbSelfInfraProviderModule.mintAppCredentials: `
|
|
220
266
|
+ `principal '${context.principal.kind}:${context.principal.name}' has no resourceGrants. `
|
|
221
|
-
+ `Refusing to fall back to the database-wide application user
|
|
222
|
-
+ `
|
|
267
|
+
+ `Refusing to fall back to the database-wide application user. The orchestrator must pass `
|
|
268
|
+
+ `an explicit empty resource grant when only the mandatory runtime floor is required.`);
|
|
223
269
|
}
|
|
270
|
+
const databaseGrants = deriveMinionDatabaseGrants(grants);
|
|
224
271
|
await adminClient.upsertCollectionScopedDatabaseUser({
|
|
225
272
|
adminConnectUrl,
|
|
226
273
|
databaseName,
|
|
@@ -229,10 +276,15 @@ export const MongodbSelfInfraProviderModule = {
|
|
|
229
276
|
roleName: `${username}_role`,
|
|
230
277
|
// Resource types → collection names HERE, at the adapter that speaks
|
|
231
278
|
// Mongo. The grant contract itself stays in the platform vocabulary.
|
|
279
|
+
// Authored declaration + the engine floor, through the SAME derivation
|
|
280
|
+
// the PostgreSQL adapter uses — so the two cannot disagree about what
|
|
281
|
+
// the framework itself requires of every runtime principal.
|
|
232
282
|
privileges: buildMinionMongodbPrivileges({
|
|
233
283
|
databaseName,
|
|
234
|
-
readCollections: resolveGrantStorageNames(
|
|
235
|
-
writeCollections: resolveGrantStorageNames(
|
|
284
|
+
readCollections: resolveGrantStorageNames(databaseGrants.read),
|
|
285
|
+
writeCollections: resolveGrantStorageNames(databaseGrants.write),
|
|
286
|
+
appendCollections: resolveGrantStorageNames(databaseGrants.append),
|
|
287
|
+
maintainCollections: resolveGrantStorageNames(databaseGrants.maintain),
|
|
236
288
|
}),
|
|
237
289
|
});
|
|
238
290
|
}
|
|
@@ -263,8 +315,9 @@ export const MongodbSelfInfraProviderModule = {
|
|
|
263
315
|
/**
|
|
264
316
|
* Tear down the per-app Mongo database user.
|
|
265
317
|
*
|
|
266
|
-
* **Caller status**:
|
|
267
|
-
*
|
|
318
|
+
* **Caller status**: apps-manager runtime-principal declaration reconciliation
|
|
319
|
+
* calls this for a removed minion when its application has a MongoDB store.
|
|
320
|
+
* The named principal and database scope identify the grant being revoked.
|
|
268
321
|
*/
|
|
269
322
|
async revokeAppCredentials(context) {
|
|
270
323
|
if (!context.applicationNamespace) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongodb-self.infra.module.js","sourceRoot":"","sources":["../../../../src/mongodb-self/mongodb-self.infra.module.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `MongodbSelfInfraProviderModule` — fifth concrete `InfraProviderModule`\n * (Slice 4.B.3 of `infra-provider-planner.md`, generalizing the\n * pattern to self-managed MongoDB).\n *\n * Implements the `MONGODB_SELF` `InfraProvider` over the\n * `IMongoSelfAdminClient` transport (admin-side mongoose connection,\n * exposed by `MongoSelfManagerService` after the Slice 4.B.3 5C\n * policy/transport split). Owns ALL per-app naming policy +\n * URL-derivation policy — the per-app username convention\n * (`<applicationNamespace>_user`, mirroring the Atlas variant) and\n * the per-app SRV URL derivation by swapping admin credentials\n * with per-app credentials in the authored\n * `BASE_MONGO_SERVER_URL`-derived admin connect URL.\n *\n * **What this module reads from `AppCredentialMintingContext`**:\n * - `applicationNamespace` — used as the source for the per-app\n * username (legacy convention `${ns}_user`).\n * - `secretInputs['database.mongodb.password']` — orchestrator-\n * supplied password (module is upsert-only, never generator).\n * - `applicationConfigInputs['databaseName']` — required.\n * - `applicationConfigInputs['adminConnectUrl']` — required, the\n * admin-scoped MongoDB connect URL the orchestrator reads from\n * `MONGO_CONNECT_URL` env at app-init time.\n * - `adminClient` — narrowed to `IMongoSelfAdminClient`.\n *\n * **Two audiences this module serves:**\n *\n * 1. **Audience-1 (platform-admin)**: cluster-wide Mongo admin\n * connect URL (`MONGO_CONNECT_URL`). Consumers: `apps-manager`\n * (mints per-app users via `mintAppCredentials`), `companion`\n * (mirrors apps-manager admin role for local dev).\n *\n * 2. **Audience-3 (app-scoped, via `mintAppCredentials`)**:\n * per-customer-app Mongo database user. Result threaded into\n * `Wildo_Application_Secrets.database.mongodb.{username,password}`\n * via `_merge_WildoApplicationSecrets`; the per-app connect URL\n * returned via `runtimeArtifacts.connectionString` for\n * transient app-init operations.\n *\n * **NOT served (D-4)**: audience-2 not applicable.\n */\n\nimport {\n AppCredentialMintingContext,\n AppCredentialMintingResult,\n IMongoSelfAdminClient,\n InfraCapability,\n InfraPlatformAdminConsumer,\n InfraProvider,\n InfraProviderModule,\n InfraProviderPlanContribution,\n ApplicationRuntimePrincipalKind,\n resolveGrantStorageNames,\n type ApplicationRuntimePrincipal,\n} from '@wildo-ai/platform-config-lib';\n\nconst MONGODB_USERNAME_PATH = 'database.mongodb.username';\nconst MONGODB_PASSWORD_PATH = 'database.mongodb.password';\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> = [MONGODB_USERNAME_PATH, MONGODB_PASSWORD_PATH];\n\n\nconst MONGODB_PASSWORD_INPUT_KEY = 'database.mongodb.password';\n\nconst DATABASE_NAME_INPUT_KEY = 'databaseName';\nconst ADMIN_CONNECT_URL_INPUT_KEY = 'adminConnectUrl';\n\nconst CONNECTION_STRING_ARTIFACT_KEY = 'connectionString';\n\n/**\n * Compute the per-app username. Mirrors the Atlas module's\n * convention (`${ns}_user`) so the persistence shape is identical\n * regardless of provider.\n */\nfunction buildMongodbUsername(applicationNamespace: string): string {\n return `${applicationNamespace}_user`;\n}\n\n/**\n * MongoDB privilege ACTIONS, per grant axis.\n *\n * Named here rather than inlined because these ARE the security semantics: what\n * \"read\" and \"write\" mean once they reach the database. Mongo's action\n * vocabulary is an external one, so it lives at this adapter — the module that\n * speaks Mongo — and never leaks into the platform's grant model.\n *\n * `find` alone for read: no `listCollections`, no `collStats`, nothing that\n * enumerates. A minion granted read on one resource should not be able to\n * discover what else exists.\n *\n * `insert`/`update`/`remove` for write, and deliberately NOT `find`. The\n * platform-level derivation refuses to widen write into read, and re-adding it\n * here would defeat that decision at the last hop — see\n * `deriveMinionResourceGrants`.\n */\nconst MONGODB_READ_ACTIONS: readonly string[] = ['find'];\nconst MONGODB_WRITE_ACTIONS: readonly string[] = ['insert', 'update', 'remove'];\n\n/** One `createRole`/`updateRole` privilege entry. */\nexport interface MongodbCollectionPrivilege {\n readonly resource: { readonly db: string; readonly collection: string };\n readonly actions: readonly string[];\n}\n\n/**\n * Turn a minion's grants into collection-scoped MongoDB privileges (D14, R1).\n *\n * ## What this deliberately never produces\n *\n * No `dbAdmin` and no `readWrite` — the two roles the application backend\n * currently holds on the whole database. `dbAdmin` is banned outright by D14;\n * `readWrite` is database-WIDE, so granting it to a minion would make every\n * collection reachable regardless of what `resourceAccess` declared, which is\n * the containment this exists to create.\n *\n * No privilege on the database RESOURCE either (`{ db, collection: '' }`), which\n * is Mongo's way of saying \"every collection in this database\". Every entry\n * names one collection explicitly.\n *\n * ## Read and write are separate entries on purpose\n *\n * A collection granted both axes yields TWO privilege entries rather than one\n * merged action list. Mongo unions them identically, but the emitted role then\n * reads back as the declaration that produced it — which is what makes an\n * operator inspecting `db.getRole()` able to check the grant against the config\n * without re-deriving it.\n */\nexport function buildMinionMongodbPrivileges(input: {\n databaseName: string;\n readCollections: readonly string[];\n writeCollections: readonly string[];\n}): MongodbCollectionPrivilege[] {\n const privileges: MongodbCollectionPrivilege[] = [];\n for (const collection of input.readCollections) {\n privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_READ_ACTIONS] });\n }\n for (const collection of input.writeCollections) {\n privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_WRITE_ACTIONS] });\n }\n return privileges;\n}\n\n/**\n * Derive the per-app connect URL by swapping admin credentials and\n * the database path in the authored `adminConnectUrl`. Mirrors the\n * legacy logic at\n * `wildo-application-lifecycle.service.ts:469-479`:\n *\n * const parsed = new URL(adminConnectUrl);\n * parsed.username = perAppUsername;\n * parsed.password = perAppPassword;\n * parsed.pathname = '/' + databaseName;\n *\n * URL-encoding handled automatically by the WHATWG URL class.\n */\nfunction buildPerAppConnectUrl(\n adminConnectUrl: string,\n databaseName: string,\n username: string,\n password: string,\n): string {\n const parsed = new URL(adminConnectUrl);\n parsed.username = encodeURIComponent(username);\n parsed.password = encodeURIComponent(password);\n parsed.pathname = '/' + databaseName;\n return parsed.toString();\n}\n\n/**\n * Per-principal Mongo username — the SAME derivation the revoke path must use.\n *\n * The application backend keeps `<namespace>_user` VERBATIM so an existing\n * deployment's user is never orphaned. Third time this shape appears (Redis,\n * RabbitMQ, now Mongo): a revoke deriving it differently deletes the BACKEND's\n * database user while believing it is removing a minion's.\n */\nfunction buildMongodbPrincipalUsername(\n applicationNamespace: string,\n principal: ApplicationRuntimePrincipal | undefined,\n): string {\n if (!principal || principal.kind === ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n return buildMongodbUsername(applicationNamespace);\n }\n const sanitizedName = (principal.name ?? '').replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');\n return `${applicationNamespace}_${principal.kind}_${sanitizedName}_user`;\n}\n\nexport const MongodbSelfInfraProviderModule: InfraProviderModule = {\n provider: InfraProvider.MONGODB_SELF,\n supportedCapabilities: [InfraCapability.DOCUMENT_DATABASE],\n perPrincipalSecretBlobPaths: PER_PRINCIPAL_SECRET_BLOB_PATHS,\n\n plan(): InfraProviderPlanContribution {\n return {\n dockerComposeServices: ['mongodb'],\n k8sResources: ['templates/infra/k8s/internal/mongodb/deployment.yaml.hbs'],\n platformAdmin: {\n envVars: {},\n // The single MONGO_CONNECT_URL secret carries the admin\n // credentials embedded; legacy field name preserved.\n secretEnvVars: ['MONGO_CONNECT_URL'],\n consumers: [\n InfraPlatformAdminConsumer.APPS_MANAGER,\n InfraPlatformAdminConsumer.COMPANION,\n ],\n },\n appRuntime: {\n // Same persistence shape as Atlas — per-app username +\n // password go into secrets; the host/protocol/options come\n // from `_merge_WildoApplicationConfig` which derives them\n // from env-config (BASE_MONGO_SERVER_URL).\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 IMongoSelfAdminClient;\n const password = context.secretInputs[MONGODB_PASSWORD_INPUT_KEY];\n if (!password) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `secretInputs is missing '${MONGODB_PASSWORD_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the Mongo password — modules are upsert-only.`,\n );\n }\n if (!context.applicationNamespace) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `applicationNamespace is empty. ` +\n `The orchestrator MUST supply a non-empty namespace.`,\n );\n }\n const databaseName = context.applicationConfigInputs?.[DATABASE_NAME_INPUT_KEY];\n if (!databaseName) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `applicationConfigInputs is missing '${DATABASE_NAME_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the per-app database name.`,\n );\n }\n const adminConnectUrl = context.applicationConfigInputs?.[ADMIN_CONNECT_URL_INPUT_KEY];\n if (!adminConnectUrl) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `applicationConfigInputs is missing '${ADMIN_CONNECT_URL_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the admin Mongo connect URL.`,\n );\n }\n\n const username = buildMongodbPrincipalUsername(context.applicationNamespace, context.principal);\n\n if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n // A named principal gets a COLLECTION-SCOPED user (Step 3 / R1 / D14):\n // one custom role carrying exactly the privileges its `resourceAccess`\n // derived, and nothing inherited.\n //\n // `resourceGrants` is REQUIRED here and its absence is an error rather\n // than a default. Falling back to the app-wide upsert would hand a minion\n // `readWrite` + `dbAdmin` on the whole database — the exact over-grant\n // this path exists to replace, arriving silently through a code path that\n // looks like it is doing the narrow thing.\n const grants = context.resourceGrants;\n if (!grants) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: `\n + `principal '${context.principal.kind}:${context.principal.name}' has no resourceGrants. `\n + `Refusing to fall back to the database-wide application user — a minion with no declared `\n + `access must get NO database user at all (D11/R3), which is the orchestrator's decision to make.`,\n );\n }\n await adminClient.upsertCollectionScopedDatabaseUser({\n adminConnectUrl,\n databaseName,\n username,\n password,\n roleName: `${username}_role`,\n // Resource types → collection names HERE, at the adapter that speaks\n // Mongo. The grant contract itself stays in the platform vocabulary.\n privileges: buildMinionMongodbPrivileges({\n databaseName,\n readCollections: resolveGrantStorageNames(grants.read),\n writeCollections: resolveGrantStorageNames(grants.write),\n }),\n });\n } else {\n // createUser-or-updateUser over an admin-scoped mongoose\n // connection — idempotent upsert.\n await adminClient.upsertApplicationDatabaseUser({\n adminConnectUrl,\n databaseName,\n username,\n password,\n });\n }\n\n // Derive the per-app SRV URL by swapping credentials + db path\n // in the authored admin URL. The orchestrator uses this for\n // transient app-init seeding; not persisted.\n const connectionString = buildPerAppConnectUrl(\n adminConnectUrl,\n databaseName,\n username,\n password,\n );\n\n return {\n secretBlobValues: {\n [MONGODB_USERNAME_PATH]: username,\n [MONGODB_PASSWORD_PATH]: password,\n },\n runtimeArtifacts: {\n [CONNECTION_STRING_ARTIFACT_KEY]: connectionString,\n },\n };\n },\n\n /**\n * Tear down the per-app Mongo database user.\n *\n * **Caller status**: no caller wires this in Slices 1–4.B.3.\n * Forward-compat seam.\n */\n async revokeAppCredentials(context: AppCredentialMintingContext): Promise<void> {\n if (!context.applicationNamespace) {\n throw new Error(\n `MongodbSelfInfraProviderModule.revokeAppCredentials: ` +\n `applicationNamespace is empty. ` +\n `The orchestrator MUST supply a non-empty namespace.`,\n );\n }\n const adminConnectUrl = context.applicationConfigInputs?.[ADMIN_CONNECT_URL_INPUT_KEY];\n if (!adminConnectUrl) {\n throw new Error(\n `MongodbSelfInfraProviderModule.revokeAppCredentials: ` +\n `applicationConfigInputs is missing '${ADMIN_CONNECT_URL_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the admin Mongo connect URL.`,\n );\n }\n const adminClient = context.adminClient as IMongoSelfAdminClient;\n // Derived through the SAME function the mint uses. Deriving it independently\n // here — which is what this line did before named principals existed — meant\n // revoking a minion left its credential live AND dropped the APPLICATION\n // BACKEND's database user, because the backend's name is what\n // `buildMongodbUsername` returns for every input.\n const username = buildMongodbPrincipalUsername(context.applicationNamespace, context.principal);\n\n if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n // A scoped principal owns a user AND a custom role. Routing it through the\n // user-only door would leave a privileged role behind that the next mint\n // adopts without anybody declaring it.\n const databaseName = context.applicationConfigInputs?.[DATABASE_NAME_INPUT_KEY];\n if (!databaseName) {\n throw new Error(\n `MongodbSelfInfraProviderModule.revokeAppCredentials: `\n + `applicationConfigInputs is missing '${DATABASE_NAME_INPUT_KEY}' for principal `\n + `'${context.principal.kind}:${context.principal.name}'. The role to drop lives IN that `\n + `database — revoking without it would drop the user and silently orphan its privileges.`,\n );\n }\n await adminClient.dropCollectionScopedDatabaseUser({\n adminConnectUrl,\n databaseName,\n username,\n roleName: `${username}_role`,\n });\n return;\n }\n\n await adminClient.deleteApplicationDatabaseUser({ adminConnectUrl, username });\n },\n\n // No `mintPlatformServiceCredentials` (D-4).\n};\n"]}
|
|
1
|
+
{"version":3,"file":"mongodb-self.infra.module.js","sourceRoot":"","sources":["../../../../src/mongodb-self/mongodb-self.infra.module.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `MongodbSelfInfraProviderModule` — fifth concrete `InfraProviderModule`\n * (Slice 4.B.3 of `infra-provider-planner.md`, generalizing the\n * pattern to self-managed MongoDB).\n *\n * Implements the `MONGODB_SELF` `InfraProvider` over the\n * `IMongoSelfAdminClient` transport (admin-side mongoose connection,\n * exposed by `MongoSelfManagerService` after the Slice 4.B.3 5C\n * policy/transport split). Owns ALL per-app naming policy +\n * URL-derivation policy — the per-app username convention\n * (`<applicationNamespace>_user`, mirroring the Atlas variant) and\n * the per-app SRV URL derivation by swapping admin credentials\n * with per-app credentials in the authored\n * `BASE_MONGO_SERVER_URL`-derived admin connect URL.\n *\n * **What this module reads from `AppCredentialMintingContext`**:\n * - `applicationNamespace` — used as the source for the per-app\n * username (legacy convention `${ns}_user`).\n * - `secretInputs['database.mongodb.password']` — orchestrator-\n * supplied password (module is upsert-only, never generator).\n * - `applicationConfigInputs['databaseName']` — required.\n * - `applicationConfigInputs['adminConnectUrl']` — required, the\n * admin-scoped MongoDB connect URL the orchestrator reads from\n * `MONGO_CONNECT_URL` env at app-init time.\n * - `adminClient` — narrowed to `IMongoSelfAdminClient`.\n *\n * **Two audiences this module serves:**\n *\n * 1. **Audience-1 (platform-admin)**: cluster-wide Mongo admin\n * connect URL (`MONGO_CONNECT_URL`). Consumers: `apps-manager`\n * (mints per-app users via `mintAppCredentials`), `companion`\n * (mirrors apps-manager admin role for local dev).\n *\n * 2. **Audience-3 (app-scoped, via `mintAppCredentials`)**:\n * per-customer-app Mongo database user. Result threaded into\n * `Wildo_Application_Secrets.database.mongodb.{username,password}`\n * via `_merge_WildoApplicationSecrets`; the per-app connect URL\n * returned via `runtimeArtifacts.connectionString` for\n * transient app-init operations.\n *\n * **NOT served (D-4)**: audience-2 not applicable.\n */\n\nimport {\n AppCredentialMintingContext,\n AppCredentialMintingResult,\n IMongoSelfAdminClient,\n InfraCapability,\n InfraPlatformAdminConsumer,\n InfraProvider,\n InfraProviderModule,\n InfraProviderPlanContribution,\n ApplicationRuntimePrincipalKind,\n deriveMinionDatabaseGrants,\n resolveGrantStorageNames,\n type ApplicationRuntimePrincipal,\n} from '@wildo-ai/platform-config-lib';\n\nconst MONGODB_USERNAME_PATH = 'database.mongodb.username';\nconst MONGODB_PASSWORD_PATH = 'database.mongodb.password';\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> = [MONGODB_USERNAME_PATH, MONGODB_PASSWORD_PATH];\n\n\nconst MONGODB_PASSWORD_INPUT_KEY = 'database.mongodb.password';\n\nconst DATABASE_NAME_INPUT_KEY = 'databaseName';\nconst ADMIN_CONNECT_URL_INPUT_KEY = 'adminConnectUrl';\n\nconst CONNECTION_STRING_ARTIFACT_KEY = 'connectionString';\n\n/**\n * Compute the per-app username. Mirrors the Atlas module's\n * convention (`${ns}_user`) so the persistence shape is identical\n * regardless of provider.\n */\nfunction buildMongodbUsername(applicationNamespace: string): string {\n return `${applicationNamespace}_user`;\n}\n\n/**\n * MongoDB privilege ACTIONS, per grant axis.\n *\n * Named here rather than inlined because these ARE the security semantics: what\n * \"read\" and \"write\" mean once they reach the database. Mongo's action\n * vocabulary is an external one, so it lives at this adapter — the module that\n * speaks Mongo — and never leaks into the platform's grant model.\n *\n * `find` alone for read: no `listCollections`, no `collStats`, nothing that\n * enumerates. A minion granted read on one resource should not be able to\n * discover what else exists.\n *\n * `insert`/`update`/`remove` for write, and deliberately NOT `find`. The\n * platform-level derivation refuses to widen write into read, and re-adding it\n * here would defeat that decision at the last hop — see\n * `deriveMinionResourceGrants`.\n */\nconst MONGODB_READ_ACTIONS: readonly string[] = ['find'];\nconst MONGODB_WRITE_ACTIONS: readonly string[] = ['insert', 'update', 'remove'];\n/**\n * Append-only: `insert` and `find`, WITHOUT `update` or `remove`. Carries the engine's audit-log\n * floor, where a principal must be able to record what it did and must never be able to unrecord it.\n *\n * `find` is not a widening — see {@link MONGODB_MAINTAIN_ACTIONS}, whose note covers both floors.\n */\nconst MONGODB_APPEND_ACTIONS: readonly string[] = ['insert', 'find'];\n/**\n * Durable runtime state may be inserted, read back and updated, never removed.\n *\n * ## Why the two FLOORS carry `find` and the author-declared write axis above does not\n *\n * Without it this axis could not perform the update the engine actually issues. Measured 2026-09-16\n * against the running replica set, as a throwaway role holding exactly these actions:\n *\n * | actions | operation | outcome |\n * |---|---|---|\n * | `insert`, `update` | `updateOne({_id}, …)` | works |\n * | `insert`, `update` | **`findOneAndUpdate`** | **Unauthorized** |\n * | `insert`, `find`, `update` | `findOneAndUpdate` | works; `deleteOne` still Unauthorized |\n *\n * The third row is the one that matters: `RepositoryResourceBackendService` updates through\n * `findOneAndUpdate`, so a role without `find` could not update at all — only a filter-less write no\n * caller issues. Note MongoDB and PostgreSQL differ here, which is why each was measured rather than\n * inferred from the other: Mongo's `update` action covers its own filter, while PostgreSQL requires\n * `SELECT` for the columns a `WHERE` reads.\n *\n * `find` on the APPEND axis was NOT broken on MongoDB — `insertOne` succeeds without it. It is added\n * so an axis means the same thing on both adapters: `deriveMinionDatabaseGrants` exists precisely so\n * the two cannot drift, and an append grant that carried read-back on one adapter and not the other\n * would make the same declaration yield two different authorities.\n *\n * **What these floors withhold is untouched.** Append still holds no `update` and no `remove`;\n * maintain still holds no `remove`. Both were re-probed after adding `find`, and `deleteOne` stayed\n * Unauthorized. The author-declared write axis keeps write-without-read deliberately, because there\n * the author has a remedy — declare the read — which `deriveMinionResourceGrants` argues for at\n * length. These two axes are engine-imposed and refuse any declaration, so no author action could\n * ever have repaired them.\n */\nconst MONGODB_MAINTAIN_ACTIONS: readonly string[] = ['insert', 'find', 'update'];\n\n/** One `createRole`/`updateRole` privilege entry. */\nexport interface MongodbCollectionPrivilege {\n readonly resource: { readonly db: string; readonly collection: string };\n readonly actions: readonly string[];\n}\n\n/**\n * Turn a minion's grants into collection-scoped MongoDB privileges (D14, R1).\n *\n * ## What this deliberately never produces\n *\n * No `dbAdmin` and no `readWrite` — the two roles the application backend\n * currently holds on the whole database. `dbAdmin` is banned outright by D14;\n * `readWrite` is database-WIDE, so granting it to a minion would make every\n * collection reachable regardless of what `resourceAccess` declared, which is\n * the containment this exists to create.\n *\n * No privilege on the database RESOURCE either (`{ db, collection: '' }`), which\n * is Mongo's way of saying \"every collection in this database\". Every entry\n * names one collection explicitly.\n *\n * ## Read and write are separate entries on purpose\n *\n * A collection granted both axes yields TWO privilege entries rather than one\n * merged action list. Mongo unions them identically, but the emitted role then\n * reads back as the declaration that produced it — which is what makes an\n * operator inspecting `db.getRole()` able to check the grant against the config\n * without re-deriving it.\n */\nexport function buildMinionMongodbPrivileges(input: {\n databaseName: string;\n readCollections: readonly string[];\n writeCollections: readonly string[];\n /** Insert-only — see {@link MONGODB_APPEND_ACTIONS}. Optional so existing pins read unchanged. */\n appendCollections?: readonly string[];\n maintainCollections?: readonly string[];\n}): MongodbCollectionPrivilege[] {\n const privileges: MongodbCollectionPrivilege[] = [];\n for (const collection of input.readCollections) {\n privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_READ_ACTIONS] });\n }\n for (const collection of input.writeCollections) {\n privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_WRITE_ACTIONS] });\n }\n for (const collection of input.appendCollections ?? []) {\n privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_APPEND_ACTIONS] });\n }\n for (const collection of input.maintainCollections ?? []) {\n privileges.push({ resource: { db: input.databaseName, collection }, actions: [...MONGODB_MAINTAIN_ACTIONS] });\n }\n return privileges;\n}\n\n/**\n * Derive the per-app connect URL by swapping admin credentials and\n * the database path in the authored `adminConnectUrl`. Mirrors the\n * legacy logic at\n * `wildo-application-lifecycle.service.ts:469-479`:\n *\n * const parsed = new URL(adminConnectUrl);\n * parsed.username = perAppUsername;\n * parsed.password = perAppPassword;\n * parsed.pathname = '/' + databaseName;\n *\n * URL-encoding handled automatically by the WHATWG URL class.\n */\nfunction buildPerAppConnectUrl(\n adminConnectUrl: string,\n databaseName: string,\n username: string,\n password: string,\n): string {\n const parsed = new URL(adminConnectUrl);\n parsed.username = encodeURIComponent(username);\n parsed.password = encodeURIComponent(password);\n parsed.pathname = '/' + databaseName;\n return parsed.toString();\n}\n\n/**\n * Per-principal Mongo username — the SAME derivation the revoke path must use.\n *\n * The application backend keeps `<namespace>_user` VERBATIM so an existing\n * deployment's user is never orphaned. Third time this shape appears (Redis,\n * RabbitMQ, now Mongo): a revoke deriving it differently deletes the BACKEND's\n * database user while believing it is removing a minion's.\n */\nfunction buildMongodbPrincipalUsername(\n applicationNamespace: string,\n principal: ApplicationRuntimePrincipal | undefined,\n): string {\n if (!principal || principal.kind === ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n return buildMongodbUsername(applicationNamespace);\n }\n const sanitizedName = (principal.name ?? '').replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');\n return `${applicationNamespace}_${principal.kind}_${sanitizedName}_user`;\n}\n\nexport const MongodbSelfInfraProviderModule: InfraProviderModule = {\n provider: InfraProvider.MONGODB_SELF,\n supportedCapabilities: [InfraCapability.DOCUMENT_DATABASE],\n perPrincipalSecretBlobPaths: PER_PRINCIPAL_SECRET_BLOB_PATHS,\n\n plan(): InfraProviderPlanContribution {\n return {\n dockerComposeServices: ['mongodb'],\n k8sResources: ['templates/infra/k8s/internal/mongodb/deployment.yaml.hbs'],\n platformAdmin: {\n envVars: {},\n // The single MONGO_CONNECT_URL secret carries the admin\n // credentials embedded; legacy field name preserved.\n secretEnvVars: ['MONGO_CONNECT_URL'],\n consumers: [\n InfraPlatformAdminConsumer.APPS_MANAGER,\n InfraPlatformAdminConsumer.COMPANION,\n ],\n },\n appRuntime: {\n // Same persistence shape as Atlas — per-app username +\n // password go into secrets; the host/protocol/options come\n // from `_merge_WildoApplicationConfig` which derives them\n // from env-config (BASE_MONGO_SERVER_URL).\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 IMongoSelfAdminClient;\n const password = context.secretInputs[MONGODB_PASSWORD_INPUT_KEY];\n if (!password) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `secretInputs is missing '${MONGODB_PASSWORD_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the Mongo password — modules are upsert-only.`,\n );\n }\n if (!context.applicationNamespace) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `applicationNamespace is empty. ` +\n `The orchestrator MUST supply a non-empty namespace.`,\n );\n }\n const databaseName = context.applicationConfigInputs?.[DATABASE_NAME_INPUT_KEY];\n if (!databaseName) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `applicationConfigInputs is missing '${DATABASE_NAME_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the per-app database name.`,\n );\n }\n const adminConnectUrl = context.applicationConfigInputs?.[ADMIN_CONNECT_URL_INPUT_KEY];\n if (!adminConnectUrl) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: ` +\n `applicationConfigInputs is missing '${ADMIN_CONNECT_URL_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the admin Mongo connect URL.`,\n );\n }\n\n const username = buildMongodbPrincipalUsername(context.applicationNamespace, context.principal);\n\n if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n // A named principal gets a COLLECTION-SCOPED user (Step 3 / R1 / D14):\n // one custom role carrying authored business grants plus the mandatory\n // runtime floor, and no inherited database-wide privileges.\n //\n // `resourceGrants` is REQUIRED here and its absence is an error rather\n // than a default. Falling back to the app-wide upsert would hand a minion\n // `readWrite` + `dbAdmin` on the whole database — the exact over-grant\n // this path exists to replace, arriving silently through a code path that\n // looks like it is doing the narrow thing.\n const grants = context.resourceGrants;\n if (!grants) {\n throw new Error(\n `MongodbSelfInfraProviderModule.mintAppCredentials: `\n + `principal '${context.principal.kind}:${context.principal.name}' has no resourceGrants. `\n + `Refusing to fall back to the database-wide application user. The orchestrator must pass `\n + `an explicit empty resource grant when only the mandatory runtime floor is required.`,\n );\n }\n const databaseGrants = deriveMinionDatabaseGrants(grants);\n\n await adminClient.upsertCollectionScopedDatabaseUser({\n adminConnectUrl,\n databaseName,\n username,\n password,\n roleName: `${username}_role`,\n // Resource types → collection names HERE, at the adapter that speaks\n // Mongo. The grant contract itself stays in the platform vocabulary.\n // Authored declaration + the engine floor, through the SAME derivation\n // the PostgreSQL adapter uses — so the two cannot disagree about what\n // the framework itself requires of every runtime principal.\n privileges: buildMinionMongodbPrivileges({\n databaseName,\n readCollections: resolveGrantStorageNames(databaseGrants.read),\n writeCollections: resolveGrantStorageNames(databaseGrants.write),\n appendCollections: resolveGrantStorageNames(databaseGrants.append),\n maintainCollections: resolveGrantStorageNames(databaseGrants.maintain),\n }),\n });\n } else {\n // createUser-or-updateUser over an admin-scoped mongoose\n // connection — idempotent upsert.\n await adminClient.upsertApplicationDatabaseUser({\n adminConnectUrl,\n databaseName,\n username,\n password,\n });\n }\n\n // Derive the per-app SRV URL by swapping credentials + db path\n // in the authored admin URL. The orchestrator uses this for\n // transient app-init seeding; not persisted.\n const connectionString = buildPerAppConnectUrl(\n adminConnectUrl,\n databaseName,\n username,\n password,\n );\n\n return {\n secretBlobValues: {\n [MONGODB_USERNAME_PATH]: username,\n [MONGODB_PASSWORD_PATH]: password,\n },\n runtimeArtifacts: {\n [CONNECTION_STRING_ARTIFACT_KEY]: connectionString,\n },\n };\n },\n\n /**\n * Tear down the per-app Mongo database user.\n *\n * **Caller status**: apps-manager runtime-principal declaration reconciliation\n * calls this for a removed minion when its application has a MongoDB store.\n * The named principal and database scope identify the grant being revoked.\n */\n async revokeAppCredentials(context: AppCredentialMintingContext): Promise<void> {\n if (!context.applicationNamespace) {\n throw new Error(\n `MongodbSelfInfraProviderModule.revokeAppCredentials: ` +\n `applicationNamespace is empty. ` +\n `The orchestrator MUST supply a non-empty namespace.`,\n );\n }\n const adminConnectUrl = context.applicationConfigInputs?.[ADMIN_CONNECT_URL_INPUT_KEY];\n if (!adminConnectUrl) {\n throw new Error(\n `MongodbSelfInfraProviderModule.revokeAppCredentials: ` +\n `applicationConfigInputs is missing '${ADMIN_CONNECT_URL_INPUT_KEY}'. ` +\n `The orchestrator MUST supply the admin Mongo connect URL.`,\n );\n }\n const adminClient = context.adminClient as IMongoSelfAdminClient;\n // Derived through the SAME function the mint uses. Deriving it independently\n // here — which is what this line did before named principals existed — meant\n // revoking a minion left its credential live AND dropped the APPLICATION\n // BACKEND's database user, because the backend's name is what\n // `buildMongodbUsername` returns for every input.\n const username = buildMongodbPrincipalUsername(context.applicationNamespace, context.principal);\n\n if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {\n // A scoped principal owns a user AND a custom role. Routing it through the\n // user-only door would leave a privileged role behind that the next mint\n // adopts without anybody declaring it.\n const databaseName = context.applicationConfigInputs?.[DATABASE_NAME_INPUT_KEY];\n if (!databaseName) {\n throw new Error(\n `MongodbSelfInfraProviderModule.revokeAppCredentials: `\n + `applicationConfigInputs is missing '${DATABASE_NAME_INPUT_KEY}' for principal `\n + `'${context.principal.kind}:${context.principal.name}'. The role to drop lives IN that `\n + `database — revoking without it would drop the user and silently orphan its privileges.`,\n );\n }\n await adminClient.dropCollectionScopedDatabaseUser({\n adminConnectUrl,\n databaseName,\n username,\n roleName: `${username}_role`,\n });\n return;\n }\n\n await adminClient.deleteApplicationDatabaseUser({ adminConnectUrl, username });\n },\n\n // No `mintPlatformServiceCredentials` (D-4).\n};\n"]}
|
|
@@ -60,10 +60,9 @@
|
|
|
60
60
|
* - **Audience-2 (platform-service-scoped)**: no platform service
|
|
61
61
|
* uses Postgres for inter-service messaging today. Method omitted.
|
|
62
62
|
*
|
|
63
|
-
* **`revokeAppCredentials
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* Method ships as a forward-compat seam.
|
|
63
|
+
* **`revokeAppCredentials`** is called by apps-manager runtime-principal
|
|
64
|
+
* declaration reconciliation when a minion is removed. The lifecycle service
|
|
65
|
+
* passes the application namespace and named principal to the adapter.
|
|
67
66
|
*/
|
|
68
67
|
import { InfraProviderModule } from '@wildo-ai/platform-config-lib';
|
|
69
68
|
export declare const PostgresSelfInfraProviderModule: InfraProviderModule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/postgres-self/postgres-self.infra.module.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"postgres-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/postgres-self/postgres-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAEH,OAAO,EAOL,mBAAmB,EAMpB,MAAM,+BAA+B,CAAC;AA6HvC,eAAO,MAAM,+BAA+B,EAAE,mBAqL7C,CAAC"}
|
|
@@ -60,12 +60,11 @@
|
|
|
60
60
|
* - **Audience-2 (platform-service-scoped)**: no platform service
|
|
61
61
|
* uses Postgres for inter-service messaging today. Method omitted.
|
|
62
62
|
*
|
|
63
|
-
* **`revokeAppCredentials
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* Method ships as a forward-compat seam.
|
|
63
|
+
* **`revokeAppCredentials`** is called by apps-manager runtime-principal
|
|
64
|
+
* declaration reconciliation when a minion is removed. The lifecycle service
|
|
65
|
+
* passes the application namespace and named principal to the adapter.
|
|
67
66
|
*/
|
|
68
|
-
import { InfraCapability, InfraPlatformAdminConsumer, InfraProvider, ApplicationRuntimePrincipalKind, resolveGrantStorageNames, } from '@wildo-ai/platform-config-lib';
|
|
67
|
+
import { InfraCapability, InfraPlatformAdminConsumer, InfraProvider, ApplicationRuntimePrincipalKind, deriveMinionDatabaseGrants, resolveGrantStorageNames, } from '@wildo-ai/platform-config-lib';
|
|
69
68
|
/**
|
|
70
69
|
* Default Postgres port (matches `INFRA_DEFAULT_PORTS.POSTGRES` in
|
|
71
70
|
* the shared platform constants — duplicated as a literal because
|
|
@@ -233,8 +232,8 @@ export const PostgresSelfInfraProviderModule = {
|
|
|
233
232
|
const username = buildPostgresPrincipalUsername(context.appSlug, context.principal);
|
|
234
233
|
if (context.principal && context.principal.kind !== ApplicationRuntimePrincipalKind.APPLICATION_BACKEND) {
|
|
235
234
|
// A named principal gets a TABLE-SCOPED role (Step 3 / R1 / D14): exactly
|
|
236
|
-
// the
|
|
237
|
-
//
|
|
235
|
+
// the authored business verbs plus the mandatory runtime floor, inside
|
|
236
|
+
// a database it does not own.
|
|
238
237
|
//
|
|
239
238
|
// `resourceGrants` is REQUIRED and its absence is an error rather than a
|
|
240
239
|
// default. Falling through to `upsertApplicationDatabase` would make the
|
|
@@ -245,9 +244,15 @@ export const PostgresSelfInfraProviderModule = {
|
|
|
245
244
|
if (!grants) {
|
|
246
245
|
throw new Error(`PostgresSelfInfraProviderModule.mintAppCredentials: `
|
|
247
246
|
+ `principal '${context.principal.kind}:${context.principal.name}' has no resourceGrants. `
|
|
248
|
-
+ `Refusing to fall back to the database-wide application role
|
|
249
|
-
+ `
|
|
247
|
+
+ `Refusing to fall back to the database-wide application role. The orchestrator must pass `
|
|
248
|
+
+ `an explicit empty resource grant when only the mandatory runtime floor is required.`);
|
|
250
249
|
}
|
|
250
|
+
// The authored declaration plus the engine's own floor — the reads and
|
|
251
|
+
// appends the framework performs for ANY principal, which no
|
|
252
|
+
// `resourceAccess` can express and none should have to. Applied through
|
|
253
|
+
// the shared derivation so this adapter and the Mongo one cannot disagree
|
|
254
|
+
// about what that floor is.
|
|
255
|
+
const databaseGrants = deriveMinionDatabaseGrants(grants);
|
|
251
256
|
const { connectionUrl: principalConnectionUrl } = await adminClient.upsertTableScopedDatabaseRole({
|
|
252
257
|
databaseName,
|
|
253
258
|
username,
|
|
@@ -257,8 +262,12 @@ export const PostgresSelfInfraProviderModule = {
|
|
|
257
262
|
// Postgres. The grant contract itself stays in the platform vocabulary,
|
|
258
263
|
// and passes through the SAME translation Mongo uses, so the two
|
|
259
264
|
// adapters cannot disagree about which rows a resource type addresses.
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
// SQL identifier casing is applied by the manager, which is the one site
|
|
266
|
+
// that issues the DDL and runs the existence check those names must match.
|
|
267
|
+
readTables: resolveGrantStorageNames(databaseGrants.read),
|
|
268
|
+
writeTables: resolveGrantStorageNames(databaseGrants.write),
|
|
269
|
+
appendTables: resolveGrantStorageNames(databaseGrants.append),
|
|
270
|
+
maintainTables: resolveGrantStorageNames(databaseGrants.maintain),
|
|
262
271
|
});
|
|
263
272
|
return {
|
|
264
273
|
secretBlobValues: {
|
|
@@ -289,10 +298,9 @@ export const PostgresSelfInfraProviderModule = {
|
|
|
289
298
|
* underlying admin client treats missing DB / missing user as
|
|
290
299
|
* no-ops.
|
|
291
300
|
*
|
|
292
|
-
* **Caller status
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* lifecycle.
|
|
301
|
+
* **Caller status**: apps-manager runtime-principal declaration reconciliation
|
|
302
|
+
* calls this for removed minions, preserving the application namespace and
|
|
303
|
+
* backing-service scope. Whole-application teardown is a separate workflow.
|
|
296
304
|
*
|
|
297
305
|
* **Defensive validation** (Slice 4.A.2 / L4 pattern): empty
|
|
298
306
|
* `appSlug` would derive an empty database name; we fail-loud
|
|
@@ -1 +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"]}
|
|
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`** is called by apps-manager runtime-principal\n * declaration reconciliation when a minion is removed. The lifecycle service\n * passes the application namespace and named principal to the adapter.\n */\n\nimport {\n AppCredentialMintingContext,\n AppCredentialMintingResult,\n IPostgresAdminClient,\n InfraCapability,\n InfraPlatformAdminConsumer,\n InfraProvider,\n InfraProviderModule,\n InfraProviderPlanContribution,\n ApplicationRuntimePrincipal,\n ApplicationRuntimePrincipalKind,\n deriveMinionDatabaseGrants,\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 authored business verbs plus the mandatory runtime floor, inside\n // 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. The orchestrator must pass `\n + `an explicit empty resource grant when only the mandatory runtime floor is required.`,\n );\n }\n // The authored declaration plus the engine's own floor — the reads and\n // appends the framework performs for ANY principal, which no\n // `resourceAccess` can express and none should have to. Applied through\n // the shared derivation so this adapter and the Mongo one cannot disagree\n // about what that floor is.\n const databaseGrants = deriveMinionDatabaseGrants(grants);\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 // SQL identifier casing is applied by the manager, which is the one site\n // that issues the DDL and runs the existence check those names must match.\n readTables: resolveGrantStorageNames(databaseGrants.read),\n writeTables: resolveGrantStorageNames(databaseGrants.write),\n appendTables: resolveGrantStorageNames(databaseGrants.append),\n maintainTables: resolveGrantStorageNames(databaseGrants.maintain),\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**: apps-manager runtime-principal declaration reconciliation\n * calls this for removed minions, preserving the application namespace and\n * backing-service scope. Whole-application teardown is a separate workflow.\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"]}
|
|
@@ -72,16 +72,15 @@
|
|
|
72
72
|
*
|
|
73
73
|
* **Vhost activation (Slice 2.5c-3, opt-in)**: when the operator
|
|
74
74
|
* authors `backingServices.rabbitmq.vhost` in
|
|
75
|
-
* `wildo.infra.<envName>.config.ts`, the CLI
|
|
76
|
-
*
|
|
77
|
-
* via `applicationConfigInputs.vhost`; this module
|
|
75
|
+
* `wildo.infra.<envName>.config.ts`, the CLI sends it with that
|
|
76
|
+
* application's registration (`backingServiceAddressing`, #1107); the lifecycle
|
|
77
|
+
* service threads it via `applicationConfigInputs.vhost`; this module
|
|
78
78
|
* `ensureVhost`-then-permissions-on-it. Without the field, the
|
|
79
79
|
* module falls back to `/` (legacy permission-regex isolation).
|
|
80
80
|
*
|
|
81
|
-
* **`revokeAppCredentials
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* Method ships as a forward-compat seam.
|
|
81
|
+
* **`revokeAppCredentials`** is called by apps-manager runtime-principal
|
|
82
|
+
* declaration reconciliation when a minion is removed. The lifecycle service
|
|
83
|
+
* passes the application namespace and named principal to the adapter.
|
|
85
84
|
*/
|
|
86
85
|
import { InfraProviderModule } from '@wildo-ai/platform-config-lib';
|
|
87
86
|
export declare const RabbitmqSelfInfraProviderModule: InfraProviderModule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rabbitmq-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/rabbitmq-self/rabbitmq-self.infra.module.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rabbitmq-self.infra.module.d.ts","sourceRoot":"","sources":["../../../../src/rabbitmq-self/rabbitmq-self.infra.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AAEH,OAAO,EAOL,mBAAmB,EAOpB,MAAM,+BAA+B,CAAC;AAwMvC,eAAO,MAAM,+BAA+B,EAAE,mBAoO7C,CAAC"}
|