@wildo-ai/platform-infra-providers 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +34 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.d.ts +63 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.d.ts.map +1 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.js +110 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.js.map +1 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.d.ts +30 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.d.ts.map +1 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.js +61 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.js.map +1 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts +26 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts.map +1 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js +54 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js.map +1 -0
- package/dist/esm/index.d.ts +88 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +125 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/minio-self/minio-self.infra.module.d.ts +64 -0
- package/dist/esm/minio-self/minio-self.infra.module.d.ts.map +1 -0
- package/dist/esm/minio-self/minio-self.infra.module.js +105 -0
- package/dist/esm/minio-self/minio-self.infra.module.js.map +1 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts +52 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts.map +1 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js +201 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js.map +1 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts +81 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts.map +1 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.js +311 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.js.map +1 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.d.ts +70 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.d.ts.map +1 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.js +328 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.js.map +1 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts +88 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts.map +1 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js +473 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js.map +1 -0
- package/dist/esm/redis-self/redis-self.infra.module.d.ts +89 -0
- package/dist/esm/redis-self/redis-self.infra.module.d.ts.map +1 -0
- package/dist/esm/redis-self/redis-self.infra.module.js +331 -0
- package/dist/esm/redis-self/redis-self.infra.module.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +52 -0
- package/src/aws-s3/__tests__/aws-s3.infra.module.test.ts +67 -0
- package/src/aws-s3/aws-s3.infra.module.ts +120 -0
- package/src/clamav-self/__tests__/clamav-self.infra.module.test.ts +65 -0
- package/src/clamav-self/clamav-self.infra.module.ts +72 -0
- package/src/gotenberg-self/__tests__/gotenberg-self.infra.module.test.ts +65 -0
- package/src/gotenberg-self/gotenberg-self.infra.module.ts +65 -0
- package/src/index.ts +133 -0
- package/src/minio-self/__tests__/minio-self.infra.module.test.ts +70 -0
- package/src/minio-self/minio-self.infra.module.ts +116 -0
- package/src/mongodb-atlas/__tests__/mongodb-atlas.infra.module.test.ts +198 -0
- package/src/mongodb-atlas/mongodb-atlas.infra.module.ts +236 -0
- package/src/mongodb-self/__tests__/mongodb-self.infra.module.test.ts +419 -0
- package/src/mongodb-self/mongodb-self.infra.module.ts +384 -0
- package/src/postgres-self/__tests__/postgres-self.infra.module.test.ts +344 -0
- package/src/postgres-self/postgres-self.infra.module.ts +377 -0
- package/src/rabbitmq-self/__tests__/rabbitmq-self.infra.module.test.ts +499 -0
- package/src/rabbitmq-self/rabbitmq-self.infra.module.ts +531 -0
- package/src/redis-self/__tests__/redis-self.infra.module.test.ts +341 -0
- package/src/redis-self/redis-self.infra.module.ts +372 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `AwsS3InfraProviderModule` — seventh concrete `InfraProviderModule`
|
|
3
|
+
* (Slice 5.1 of `infra-provider-planner.md`).
|
|
4
|
+
*
|
|
5
|
+
* Implements the `AWS_S3` `InfraProvider`. Shares the one-bucket,
|
|
6
|
+
* prefix-namespaced object layout of `MinioSelfInfraProviderModule` —
|
|
7
|
+
* the only differences are (a) no compose / K8s contribution (S3 is
|
|
8
|
+
* managed), and (b) the bucket endpoint is the regional AWS hostname
|
|
9
|
+
* instead of the in-cluster `minio` service.
|
|
10
|
+
*
|
|
11
|
+
* ## ⚠️ NOT USABLE TODAY — selecting this provider leaves apps with no
|
|
12
|
+
* storage credential (2026-08-10)
|
|
13
|
+
*
|
|
14
|
+
* The application-tier `STORAGE_ACCESS_KEY` / `STORAGE_SECRET_KEY` env
|
|
15
|
+
* vars were removed **for every provider**: they were the object store's
|
|
16
|
+
* ROOT pair, handed to every app, minion and worker. Applications now
|
|
17
|
+
* obtain short-lived, prefix-confined credentials from apps-manager.
|
|
18
|
+
*
|
|
19
|
+
* That replacement is implemented for **MinIO only**. apps-manager's
|
|
20
|
+
* `ObjectStorageStsBrokerService` speaks MinIO's STS `AssumeRole` with an
|
|
21
|
+
* inline session policy; AWS STS requires a role ARN the broker does not
|
|
22
|
+
* supply, so an `AWS_S3` deployment would fail at storage initialisation
|
|
23
|
+
* with no credential and no path to one.
|
|
24
|
+
*
|
|
25
|
+
* No environment selects this provider today (every example uses
|
|
26
|
+
* `BackingServiceSource.SELF` → MinIO), which is why this is recorded as
|
|
27
|
+
* a gap rather than an outage. But it IS reachable — `s3: { source:
|
|
28
|
+
* MANAGED }` or `provider: 'aws'` resolves here — so **do not select
|
|
29
|
+
* `AWS_S3` until the AWS STS path exists.** Tracked in
|
|
30
|
+
* `.claude/plans/object-storage-root-credential-distribution.md`.
|
|
31
|
+
*
|
|
32
|
+
* The module still reuses the `STORAGE_*` env contract for the
|
|
33
|
+
* NON-secret values (endpoint, region, bucket, provider); it is only the
|
|
34
|
+
* credential half that is gone.
|
|
35
|
+
*
|
|
36
|
+
* **Why this module exists despite "no compose / no mint"**: it
|
|
37
|
+
* gives the lockfile a stable digest entry (so a switch from
|
|
38
|
+
* `minio-self` to `aws-s3` shows up as a `wildo lock check` drift)
|
|
39
|
+
* AND it formalizes which env vars the operator must set for AWS
|
|
40
|
+
* (`STORAGE_REGION` is mandatory; `STORAGE_ENDPOINT` is optional —
|
|
41
|
+
* defaults to the AWS regional endpoint when unset).
|
|
42
|
+
*
|
|
43
|
+
* **No mint methods here** — and the reason is no longer the one this
|
|
44
|
+
* block used to give. It said per-app credentials were out of scope
|
|
45
|
+
* "because the engine adapter uses a single shared SDK client", which
|
|
46
|
+
* stopped being true when the adapter moved to a credential provider that
|
|
47
|
+
* rebuilds its client whenever the credential changes. Per-application
|
|
48
|
+
* isolation exists; what is missing for AWS is only the broker's ability
|
|
49
|
+
* to speak AWS STS.
|
|
50
|
+
*
|
|
51
|
+
* Nor should the missing piece be per-app IAM users or roles, which is
|
|
52
|
+
* what this block previously anticipated. The MinIO path deliberately
|
|
53
|
+
* rejected long-lived per-principal accounts in favour of `AssumeRole`
|
|
54
|
+
* with an inline session policy — nothing long-lived exists to leak, and
|
|
55
|
+
* credential-rotation safety dissolves instead of needing a design. The
|
|
56
|
+
* AWS equivalent is the same shape: `AssumeRole` against a role ARN the
|
|
57
|
+
* operator supplies, with a session policy confining the principal to
|
|
58
|
+
* `applications/<applicationId>/`. That role ARN is the one input the
|
|
59
|
+
* broker does not have today, and is the whole of the gap.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import {
|
|
63
|
+
InfraCapability,
|
|
64
|
+
InfraPlatformAdminConsumer,
|
|
65
|
+
InfraProvider,
|
|
66
|
+
InfraProviderModule,
|
|
67
|
+
InfraProviderPlanContribution,
|
|
68
|
+
} from '@wildo-ai/platform-config-lib';
|
|
69
|
+
|
|
70
|
+
export const AwsS3InfraProviderModule: InfraProviderModule = {
|
|
71
|
+
provider: InfraProvider.AWS_S3,
|
|
72
|
+
supportedCapabilities: [InfraCapability.OBJECT_STORAGE],
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Shared bucket, shared access key — no per-app credential is minted, so there is nothing
|
|
76
|
+
* per-principal to confine. Confinement for object storage is done by STS session policy at
|
|
77
|
+
* request time, not by a secret in the application blob.
|
|
78
|
+
*/
|
|
79
|
+
perPrincipalSecretBlobPaths: [],
|
|
80
|
+
|
|
81
|
+
plan(): InfraProviderPlanContribution {
|
|
82
|
+
return {
|
|
83
|
+
// No compose service — S3 is fully managed.
|
|
84
|
+
// No K8s manifest — same reason.
|
|
85
|
+
platformAdmin: {
|
|
86
|
+
envVars: {
|
|
87
|
+
STORAGE_PROVIDER: 's3',
|
|
88
|
+
// Endpoint and port have AWS defaults; operator may override
|
|
89
|
+
// for region-specific or VPC-endpoint topologies via the
|
|
90
|
+
// env's authored `backingServices.s3` config.
|
|
91
|
+
STORAGE_USE_SSL: 'true',
|
|
92
|
+
},
|
|
93
|
+
secretEnvVars: [
|
|
94
|
+
'STORAGE_ACCESS_KEY',
|
|
95
|
+
'STORAGE_SECRET_KEY',
|
|
96
|
+
// Region is sensitive-ish (deployment topology) and is
|
|
97
|
+
// delivered via the secret env channel for symmetry with
|
|
98
|
+
// the access keys, even though it's not strictly secret.
|
|
99
|
+
// The legacy treatment was env-var, not secret-env-var; if
|
|
100
|
+
// operators object, this can flip to `envVars` later
|
|
101
|
+
// without breaking the contract.
|
|
102
|
+
'STORAGE_REGION',
|
|
103
|
+
],
|
|
104
|
+
consumers: [
|
|
105
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
106
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
// Same shared bucket as MinIO_Self. Apps receive the NON-secret
|
|
110
|
+
// `STORAGE_*` values via the bootstrap env contract; the credential
|
|
111
|
+
// half is no longer emitted for any provider — see the ⚠️ block in
|
|
112
|
+
// this module's JSDoc for why that makes AWS_S3 currently unusable.
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// No `mintAppCredentials` / `revokeAppCredentials` — not because the model
|
|
117
|
+
// is shared-bucket (it is, but that is not the reason), but because
|
|
118
|
+
// per-application isolation is delivered by BROKERED STS SESSIONS rather than
|
|
119
|
+
// by minted long-lived accounts. See this module's JSDoc.
|
|
120
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `ClamavSelfInfraProviderModule` (Slice 5.2 of
|
|
3
|
+
* `infra-provider-planner.md`).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
InfraCapability,
|
|
9
|
+
InfraPlatformAdminConsumer,
|
|
10
|
+
InfraProvider,
|
|
11
|
+
} from '@wildo-ai/platform-config-lib';
|
|
12
|
+
import { ClamavSelfInfraProviderModule } from '../clamav-self.infra.module';
|
|
13
|
+
|
|
14
|
+
describe('ClamavSelfInfraProviderModule.plan()', () => {
|
|
15
|
+
const contribution = ClamavSelfInfraProviderModule.plan({
|
|
16
|
+
environment: 'local',
|
|
17
|
+
runtime: 'docker-compose',
|
|
18
|
+
backingServiceKey: 'clamav',
|
|
19
|
+
config: {},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('declares the right provider + capability triple', () => {
|
|
23
|
+
expect(ClamavSelfInfraProviderModule.provider).toBe(InfraProvider.CLAMAV_SELF);
|
|
24
|
+
expect(ClamavSelfInfraProviderModule.supportedCapabilities).toEqual([
|
|
25
|
+
InfraCapability.ANTIVIRUS_SCANNING,
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('contributes the clamav compose service + K8s manifest', () => {
|
|
30
|
+
expect(contribution.dockerComposeServices).toEqual(['clamav']);
|
|
31
|
+
expect(contribution.k8sResources).toEqual([
|
|
32
|
+
'templates/infra/k8s/internal/clamav/deployment.yaml.hbs',
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('declares CLAMAV_HOST/PORT env (no auth — no secret env vars)', () => {
|
|
37
|
+
expect(contribution.platformAdmin?.envVars).toEqual({
|
|
38
|
+
CLAMAV_HOST: 'clamav',
|
|
39
|
+
CLAMAV_PORT: '3310',
|
|
40
|
+
});
|
|
41
|
+
expect(contribution.platformAdmin?.secretEnvVars).toBeUndefined();
|
|
42
|
+
expect(contribution.platformAdmin?.consumers).toEqual([
|
|
43
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
44
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('declares NO appRuntime contribution (stateless — apps connect by host:port)', () => {
|
|
49
|
+
expect(contribution.appRuntime).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('ClamavSelfInfraProviderModule stateless absence', () => {
|
|
54
|
+
it('does NOT declare mintAppCredentials (no auth, no per-app state)', () => {
|
|
55
|
+
expect(ClamavSelfInfraProviderModule.mintAppCredentials).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does NOT declare revokeAppCredentials', () => {
|
|
59
|
+
expect(ClamavSelfInfraProviderModule.revokeAppCredentials).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does NOT declare mintPlatformServiceCredentials', () => {
|
|
63
|
+
expect(ClamavSelfInfraProviderModule.mintPlatformServiceCredentials).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ClamavSelfInfraProviderModule` — eighth concrete `InfraProviderModule`
|
|
3
|
+
* (Slice 5.2 of `infra-provider-planner.md`).
|
|
4
|
+
*
|
|
5
|
+
* Implements the `CLAMAV_SELF` `InfraProvider`. **Stateless**: ClamAV
|
|
6
|
+
* is a network virus-scanning service with no auth and no per-tenant
|
|
7
|
+
* state. Apps connect to the shared host:port and submit files for
|
|
8
|
+
* scanning over the ClamAV protocol; nothing is persisted, nothing
|
|
9
|
+
* is per-app. The module's only job is to declare the resource +
|
|
10
|
+
* env vars in `plan()`; there are no `mintAppCredentials`,
|
|
11
|
+
* `revokeAppCredentials`, or `mintPlatformServiceCredentials`
|
|
12
|
+
* methods because there are no credentials.
|
|
13
|
+
*
|
|
14
|
+
* **Apps consume**: `CLAMAV_HOST`, `CLAMAV_PORT` env vars (delivered
|
|
15
|
+
* via the bootstrap env contract). The engine's antivirus-scanning
|
|
16
|
+
* adapter (`saas-backend-lib/.../antivirus/...`) opens a TCP
|
|
17
|
+
* connection per scan request.
|
|
18
|
+
*
|
|
19
|
+
* **Why declare it as a module at all (vs. inline compose service)?**
|
|
20
|
+
* The module gives the lockfile a stable `planContributionDigest`
|
|
21
|
+
* for `ANTIVIRUS_SCANNING` so `wildo lock check` catches drift in
|
|
22
|
+
* the ClamAV resource shape (port change, env-var rename, K8s
|
|
23
|
+
* manifest move). Without a module, the digest would be the
|
|
24
|
+
* authored-entry digest (which is empty — ClamAV has no configurable
|
|
25
|
+
* `BackingService` shape today, just the `BackingServiceKind`
|
|
26
|
+
* enum value).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
InfraCapability,
|
|
31
|
+
InfraPlatformAdminConsumer,
|
|
32
|
+
InfraProvider,
|
|
33
|
+
InfraProviderModule,
|
|
34
|
+
InfraProviderPlanContribution,
|
|
35
|
+
} from '@wildo-ai/platform-config-lib';
|
|
36
|
+
|
|
37
|
+
const DEFAULT_CLAMAV_PORT = 3310;
|
|
38
|
+
|
|
39
|
+
export const ClamavSelfInfraProviderModule: InfraProviderModule = {
|
|
40
|
+
provider: InfraProvider.CLAMAV_SELF,
|
|
41
|
+
supportedCapabilities: [InfraCapability.ANTIVIRUS_SCANNING],
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stateless infra with no authentication and no per-app state — `plan()` only.
|
|
45
|
+
*/
|
|
46
|
+
perPrincipalSecretBlobPaths: [],
|
|
47
|
+
|
|
48
|
+
plan(): InfraProviderPlanContribution {
|
|
49
|
+
return {
|
|
50
|
+
dockerComposeServices: ['clamav'],
|
|
51
|
+
k8sResources: ['templates/infra/k8s/internal/clamav/deployment.yaml.hbs'],
|
|
52
|
+
platformAdmin: {
|
|
53
|
+
envVars: {
|
|
54
|
+
CLAMAV_HOST: 'clamav',
|
|
55
|
+
CLAMAV_PORT: String(DEFAULT_CLAMAV_PORT),
|
|
56
|
+
},
|
|
57
|
+
// No secret env vars — ClamAV has no auth.
|
|
58
|
+
// Audience-1 declared so the planner emits the env vars to
|
|
59
|
+
// both apps-manager and companion (companion runs scans
|
|
60
|
+
// locally during dev for parity with prod behavior).
|
|
61
|
+
consumers: [
|
|
62
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
63
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
// No `appRuntime` blob paths — apps connect via the same
|
|
67
|
+
// `CLAMAV_HOST:CLAMAV_PORT` regardless of app identity.
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// No mint methods — ClamAV is stateless.
|
|
72
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `GotenbergSelfInfraProviderModule` (Slice 5.3 of
|
|
3
|
+
* `infra-provider-planner.md`).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
InfraCapability,
|
|
9
|
+
InfraPlatformAdminConsumer,
|
|
10
|
+
InfraProvider,
|
|
11
|
+
} from '@wildo-ai/platform-config-lib';
|
|
12
|
+
import { GotenbergSelfInfraProviderModule } from '../gotenberg-self.infra.module';
|
|
13
|
+
|
|
14
|
+
describe('GotenbergSelfInfraProviderModule.plan()', () => {
|
|
15
|
+
const contribution = GotenbergSelfInfraProviderModule.plan({
|
|
16
|
+
environment: 'local',
|
|
17
|
+
runtime: 'docker-compose',
|
|
18
|
+
backingServiceKey: 'gotenberg',
|
|
19
|
+
config: {},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('declares the right provider + capability triple', () => {
|
|
23
|
+
expect(GotenbergSelfInfraProviderModule.provider).toBe(InfraProvider.GOTENBERG_SELF);
|
|
24
|
+
expect(GotenbergSelfInfraProviderModule.supportedCapabilities).toEqual([
|
|
25
|
+
InfraCapability.DOCUMENT_RENDERING,
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('contributes the gotenberg compose service + K8s manifest', () => {
|
|
30
|
+
expect(contribution.dockerComposeServices).toEqual(['gotenberg']);
|
|
31
|
+
expect(contribution.k8sResources).toEqual([
|
|
32
|
+
'templates/infra/k8s/internal/gotenberg/deployment.yaml.hbs',
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('declares GOTENBERG_HOST/PORT env (no auth — no secret env vars)', () => {
|
|
37
|
+
expect(contribution.platformAdmin?.envVars).toEqual({
|
|
38
|
+
GOTENBERG_HOST: 'gotenberg',
|
|
39
|
+
GOTENBERG_PORT: '3000',
|
|
40
|
+
});
|
|
41
|
+
expect(contribution.platformAdmin?.secretEnvVars).toBeUndefined();
|
|
42
|
+
expect(contribution.platformAdmin?.consumers).toEqual([
|
|
43
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
44
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('declares NO appRuntime contribution (stateless — apps connect by host:port)', () => {
|
|
49
|
+
expect(contribution.appRuntime).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('GotenbergSelfInfraProviderModule stateless absence', () => {
|
|
54
|
+
it('does NOT declare mintAppCredentials (no auth, no per-app state)', () => {
|
|
55
|
+
expect(GotenbergSelfInfraProviderModule.mintAppCredentials).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does NOT declare revokeAppCredentials', () => {
|
|
59
|
+
expect(GotenbergSelfInfraProviderModule.revokeAppCredentials).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does NOT declare mintPlatformServiceCredentials', () => {
|
|
63
|
+
expect(GotenbergSelfInfraProviderModule.mintPlatformServiceCredentials).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GotenbergSelfInfraProviderModule` — ninth concrete
|
|
3
|
+
* `InfraProviderModule` (Slice 5.3 of `infra-provider-planner.md`).
|
|
4
|
+
*
|
|
5
|
+
* Implements the `GOTENBERG_SELF` `InfraProvider`. **Stateless**:
|
|
6
|
+
* Gotenberg is a network HTML-to-PDF rendering service with no auth
|
|
7
|
+
* and no per-tenant state. Apps POST HTML/Office docs and receive
|
|
8
|
+
* rendered PDFs; nothing is persisted server-side. Same shape as
|
|
9
|
+
* `ClamavSelfInfraProviderModule` — `plan()` only, no mint methods.
|
|
10
|
+
*
|
|
11
|
+
* **Apps consume**: `GOTENBERG_HOST`, `GOTENBERG_PORT` env vars (TBD
|
|
12
|
+
* naming — current engine adapter reads these from
|
|
13
|
+
* `BackingServicesEnvConfig` via the catalog at
|
|
14
|
+
* `wildo-cli/.../service-catalog.schemas.ts:78`). The plan emits
|
|
15
|
+
* the env vars under those names.
|
|
16
|
+
*
|
|
17
|
+
* **Two ports** (Slice 5.3 design note): Gotenberg exposes both a
|
|
18
|
+
* public port (3000 by default) for HTTP API access and an internal
|
|
19
|
+
* port (3001) for health-check/metrics. The CLI's docker-compose
|
|
20
|
+
* generator already wires both. The module's `plan()` only emits
|
|
21
|
+
* the public port — the internal port is a docker-compose-only
|
|
22
|
+
* concern and isn't part of the customer-app env-var surface.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
InfraCapability,
|
|
27
|
+
InfraPlatformAdminConsumer,
|
|
28
|
+
InfraProvider,
|
|
29
|
+
InfraProviderModule,
|
|
30
|
+
InfraProviderPlanContribution,
|
|
31
|
+
} from '@wildo-ai/platform-config-lib';
|
|
32
|
+
|
|
33
|
+
const DEFAULT_GOTENBERG_PORT = 3000;
|
|
34
|
+
|
|
35
|
+
export const GotenbergSelfInfraProviderModule: InfraProviderModule = {
|
|
36
|
+
provider: InfraProvider.GOTENBERG_SELF,
|
|
37
|
+
supportedCapabilities: [InfraCapability.DOCUMENT_RENDERING],
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Stateless infra with no authentication and no per-app state — `plan()` only.
|
|
41
|
+
*/
|
|
42
|
+
perPrincipalSecretBlobPaths: [],
|
|
43
|
+
|
|
44
|
+
plan(): InfraProviderPlanContribution {
|
|
45
|
+
return {
|
|
46
|
+
dockerComposeServices: ['gotenberg'],
|
|
47
|
+
k8sResources: ['templates/infra/k8s/internal/gotenberg/deployment.yaml.hbs'],
|
|
48
|
+
platformAdmin: {
|
|
49
|
+
envVars: {
|
|
50
|
+
GOTENBERG_HOST: 'gotenberg',
|
|
51
|
+
GOTENBERG_PORT: String(DEFAULT_GOTENBERG_PORT),
|
|
52
|
+
},
|
|
53
|
+
// No secret env vars — Gotenberg has no auth.
|
|
54
|
+
consumers: [
|
|
55
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
56
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
// No `appRuntime` blob paths — apps connect via the same
|
|
60
|
+
// `GOTENBERG_HOST:GOTENBERG_PORT` regardless of app identity.
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// No mint methods — Gotenberg is stateless.
|
|
65
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@wildo-ai/platform-infra-providers` — concrete `InfraProviderModule`
|
|
3
|
+
* catalog (Slice 2.5c-1 of `infra-provider-planner.md`).
|
|
4
|
+
*
|
|
5
|
+
* Tier-portable: the modules import only `@wildo-ai/platform-config-lib`
|
|
6
|
+
* (contracts) and have no DI / mongoose / axios runtime dependencies.
|
|
7
|
+
* This lets both `platform-apps-manager` (orchestrator) and
|
|
8
|
+
* `wildo-cli` (lockfile materializer) consume the same module
|
|
9
|
+
* objects — apps-manager passes a real admin-client implementation
|
|
10
|
+
* via `AppCredentialMintingContext.adminClient`; wildo-cli only
|
|
11
|
+
* needs `module.plan()` for the lockfile digest.
|
|
12
|
+
*
|
|
13
|
+
* Each module is exported by name (no intermediary barrels per
|
|
14
|
+
* platform/CLAUDE.md "no intermediary index.ts barrels"). The
|
|
15
|
+
* `INFRA_PROVIDER_MODULES` map below is the canonical catalog —
|
|
16
|
+
* consumers iterate it (or look up by `InfraProvider` value) instead
|
|
17
|
+
* of importing modules one by one.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { InfraProvider, InfraProviderModule } from '@wildo-ai/platform-config-lib';
|
|
21
|
+
|
|
22
|
+
import { RabbitmqSelfInfraProviderModule } from './rabbitmq-self/rabbitmq-self.infra.module';
|
|
23
|
+
import { RedisSelfInfraProviderModule } from './redis-self/redis-self.infra.module';
|
|
24
|
+
import { PostgresSelfInfraProviderModule } from './postgres-self/postgres-self.infra.module';
|
|
25
|
+
import { MongodbAtlasInfraProviderModule } from './mongodb-atlas/mongodb-atlas.infra.module';
|
|
26
|
+
import { MongodbSelfInfraProviderModule } from './mongodb-self/mongodb-self.infra.module';
|
|
27
|
+
// Slice 5 of `infra-provider-planner.md` — object-storage + stateless infra.
|
|
28
|
+
import { MinioSelfInfraProviderModule } from './minio-self/minio-self.infra.module';
|
|
29
|
+
import { AwsS3InfraProviderModule } from './aws-s3/aws-s3.infra.module';
|
|
30
|
+
import { ClamavSelfInfraProviderModule } from './clamav-self/clamav-self.infra.module';
|
|
31
|
+
import { GotenbergSelfInfraProviderModule } from './gotenberg-self/gotenberg-self.infra.module';
|
|
32
|
+
|
|
33
|
+
export { RabbitmqSelfInfraProviderModule } from './rabbitmq-self/rabbitmq-self.infra.module';
|
|
34
|
+
export { RedisSelfInfraProviderModule } from './redis-self/redis-self.infra.module';
|
|
35
|
+
export { PostgresSelfInfraProviderModule } from './postgres-self/postgres-self.infra.module';
|
|
36
|
+
export { MongodbAtlasInfraProviderModule } from './mongodb-atlas/mongodb-atlas.infra.module';
|
|
37
|
+
export { MongodbSelfInfraProviderModule } from './mongodb-self/mongodb-self.infra.module';
|
|
38
|
+
export { MinioSelfInfraProviderModule } from './minio-self/minio-self.infra.module';
|
|
39
|
+
export { AwsS3InfraProviderModule } from './aws-s3/aws-s3.infra.module';
|
|
40
|
+
export { ClamavSelfInfraProviderModule } from './clamav-self/clamav-self.infra.module';
|
|
41
|
+
export { GotenbergSelfInfraProviderModule } from './gotenberg-self/gotenberg-self.infra.module';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Canonical `InfraProvider` → `InfraProviderModule` catalog. Caller
|
|
45
|
+
* (CLI, apps-manager) builds a `Map<InfraProvider, InfraProviderModule>`
|
|
46
|
+
* from this constant when threading modules into
|
|
47
|
+
* `materializeInfraSection`.
|
|
48
|
+
*
|
|
49
|
+
* **Why a const array instead of a `Map` literal**: arrays serialize
|
|
50
|
+
* stably under tsc and roll up to a deterministic order. The
|
|
51
|
+
* downstream `Map` is built once at program start; the catalog
|
|
52
|
+
* itself is a value-level fixture.
|
|
53
|
+
*
|
|
54
|
+
* **Coverage** (Slice 5 baseline, 2026-05-10):
|
|
55
|
+
* - `RABBITMQ_SELF`, `REDIS_SELF`, `POSTGRES_SELF`, `MONGODB_SELF`,
|
|
56
|
+
* `MONGODB_ATLAS` — five stateful modules with mint/revoke
|
|
57
|
+
* methods.
|
|
58
|
+
* - `MINIO_SELF`, `AWS_S3` — object storage (shared-bucket model;
|
|
59
|
+
* no per-app credentials).
|
|
60
|
+
* - `CLAMAV_SELF`, `GOTENBERG_SELF` — stateless infra (no auth, no
|
|
61
|
+
* per-app state; `plan()` only).
|
|
62
|
+
*
|
|
63
|
+
* **NOT covered** (catalog entries without modules):
|
|
64
|
+
* - `POSTGRES_MANAGED`, `POSTGRES_AWS_RDS`, `POSTGRES_GCP_CLOUD_SQL`,
|
|
65
|
+
* `POSTGRES_AZURE_DATABASE`, `POSTGRES_SUPABASE`, `POSTGRES_NEON`,
|
|
66
|
+
* `POSTGRES_RENDER` — managed-Postgres brand catalog entries
|
|
67
|
+
* (Slice 4.B.0). No module yet; trigger = real customer.
|
|
68
|
+
*
|
|
69
|
+
* `materializeInfraSection` falls back to the authored-entry digest
|
|
70
|
+
* when a selected `InfraProvider` is not in the map (preserves the
|
|
71
|
+
* Slice 2.5b behavior for uncovered providers).
|
|
72
|
+
*/
|
|
73
|
+
export const INFRA_PROVIDER_MODULES: ReadonlyArray<InfraProviderModule> = [
|
|
74
|
+
RabbitmqSelfInfraProviderModule,
|
|
75
|
+
RedisSelfInfraProviderModule,
|
|
76
|
+
PostgresSelfInfraProviderModule,
|
|
77
|
+
MongodbAtlasInfraProviderModule,
|
|
78
|
+
MongodbSelfInfraProviderModule,
|
|
79
|
+
MinioSelfInfraProviderModule,
|
|
80
|
+
AwsS3InfraProviderModule,
|
|
81
|
+
ClamavSelfInfraProviderModule,
|
|
82
|
+
GotenbergSelfInfraProviderModule,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Every `Wildo_Application_Secrets` JSON path that ANY provider module can mint per runtime
|
|
87
|
+
* principal — the union of the catalog's own static declarations.
|
|
88
|
+
*
|
|
89
|
+
* ## Why this is derived rather than written down
|
|
90
|
+
*
|
|
91
|
+
* The consumer is the secret-DELIVERY path in apps-manager: it strips exactly this set from the
|
|
92
|
+
* application blob before overlaying a principal's own material, so a principal receives a
|
|
93
|
+
* credential only where one was minted FOR IT. A hand-maintained copy at that consumer would be a
|
|
94
|
+
* second source — add a provider that mints a new credential path and delivery keeps shipping the
|
|
95
|
+
* APPLICATION's value for it, silently. That is the defect class this repo has paid for twice: the
|
|
96
|
+
* party that CONSTRAINS is not the party that DECLARES.
|
|
97
|
+
*
|
|
98
|
+
* Derived from `INFRA_PROVIDER_MODULES`, so a new module is covered the day it enters the catalog.
|
|
99
|
+
*
|
|
100
|
+
* Deliberately the union across ALL modules, not the ones a given application selected: a principal
|
|
101
|
+
* must not receive an application-wide credential for a provider it does not use either, and the
|
|
102
|
+
* selected set is not knowable at the delivery call site without threading app config through it.
|
|
103
|
+
* Stripping a path no module minted for this app is harmless — it is absent from the blob anyway.
|
|
104
|
+
*/
|
|
105
|
+
export function collectPerPrincipalSecretBlobPaths(): ReadonlySet<string> {
|
|
106
|
+
const paths = new Set<string>();
|
|
107
|
+
for (const module of INFRA_PROVIDER_MODULES) {
|
|
108
|
+
for (const path of module.perPrincipalSecretBlobPaths) {
|
|
109
|
+
paths.add(path);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return paths;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Build a `Map<InfraProvider, InfraProviderModule>` for the
|
|
117
|
+
* canonical catalog. Callers store the result once at startup and
|
|
118
|
+
* pass it to `materializeInfraSection` (digest derivation) or to
|
|
119
|
+
* orchestrator paths that look up modules by `InfraProvider`.
|
|
120
|
+
*/
|
|
121
|
+
export function buildInfraProviderModuleMap(): ReadonlyMap<InfraProvider, InfraProviderModule> {
|
|
122
|
+
const map = new Map<InfraProvider, InfraProviderModule>();
|
|
123
|
+
for (const m of INFRA_PROVIDER_MODULES) {
|
|
124
|
+
if (map.has(m.provider)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`INFRA_PROVIDER_MODULES contains duplicate provider '${m.provider}' — ` +
|
|
127
|
+
`each InfraProvider may have at most one module.`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
map.set(m.provider, m);
|
|
131
|
+
}
|
|
132
|
+
return map;
|
|
133
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `MinioSelfInfraProviderModule` (Slice 5.1 of
|
|
3
|
+
* `infra-provider-planner.md`).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
InfraCapability,
|
|
9
|
+
InfraPlatformAdminConsumer,
|
|
10
|
+
InfraProvider,
|
|
11
|
+
} from '@wildo-ai/platform-config-lib';
|
|
12
|
+
import { MinioSelfInfraProviderModule } from '../minio-self.infra.module';
|
|
13
|
+
|
|
14
|
+
describe('MinioSelfInfraProviderModule.plan()', () => {
|
|
15
|
+
const contribution = MinioSelfInfraProviderModule.plan({
|
|
16
|
+
environment: 'local',
|
|
17
|
+
runtime: 'docker-compose',
|
|
18
|
+
backingServiceKey: 's3',
|
|
19
|
+
config: {},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('declares the right provider + capability triple', () => {
|
|
23
|
+
expect(MinioSelfInfraProviderModule.provider).toBe(InfraProvider.MINIO_SELF);
|
|
24
|
+
expect(MinioSelfInfraProviderModule.supportedCapabilities).toEqual([
|
|
25
|
+
InfraCapability.OBJECT_STORAGE,
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('contributes the minio compose service + K8s manifest', () => {
|
|
30
|
+
expect(contribution.dockerComposeServices).toEqual(['minio']);
|
|
31
|
+
expect(contribution.k8sResources).toEqual([
|
|
32
|
+
'templates/infra/k8s/internal/minio/deployment.yaml.hbs',
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('declares STORAGE_PROVIDER=minio and the default endpoint topology', () => {
|
|
37
|
+
expect(contribution.platformAdmin?.envVars).toEqual({
|
|
38
|
+
STORAGE_PROVIDER: 'minio',
|
|
39
|
+
STORAGE_ENDPOINT: 'minio',
|
|
40
|
+
STORAGE_PORT: '9000',
|
|
41
|
+
STORAGE_USE_SSL: 'false',
|
|
42
|
+
});
|
|
43
|
+
expect(contribution.platformAdmin?.secretEnvVars).toEqual([
|
|
44
|
+
'STORAGE_ACCESS_KEY',
|
|
45
|
+
'STORAGE_SECRET_KEY',
|
|
46
|
+
]);
|
|
47
|
+
expect(contribution.platformAdmin?.consumers).toEqual([
|
|
48
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
49
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('declares NO appRuntime contribution (shared-bucket model — no per-app state)', () => {
|
|
54
|
+
expect(contribution.appRuntime).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('MinioSelfInfraProviderModule shared-bucket absence (Slice 5.1)', () => {
|
|
59
|
+
it('does NOT declare mintAppCredentials (shared bucket — no per-app creds)', () => {
|
|
60
|
+
expect(MinioSelfInfraProviderModule.mintAppCredentials).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does NOT declare revokeAppCredentials', () => {
|
|
64
|
+
expect(MinioSelfInfraProviderModule.revokeAppCredentials).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('does NOT declare mintPlatformServiceCredentials', () => {
|
|
68
|
+
expect(MinioSelfInfraProviderModule.mintPlatformServiceCredentials).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
});
|