@ankhorage/contracts 19.3.0 → 19.4.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/contracts
2
2
 
3
+ ## 19.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 59277a2: Add portable image-seeded initialization for persistent workload volumes.
8
+
3
9
  ## 19.3.0
4
10
 
5
11
  ### Minor Changes
@@ -66,12 +66,13 @@ function hasValidPublishedPorts(workload) {
66
66
  function isFile(value) {
67
67
  return isInfraShape(value, { path: isAbsoluteWorkloadPath, content: isInfraWorkloadValue });
68
68
  }
69
- /*** Persistence has explicit ownership-local identity and a retention policy. */
69
+ /*** Persistence has explicit ownership-local identity, optional initialization and retention policy. */
70
70
  function isVolume(value) {
71
71
  return isInfraShape(value, {
72
72
  id: isNonEmptyString,
73
73
  mountPath: isAbsoluteWorkloadPath,
74
74
  sizeGiB: infraFields.positiveInteger,
75
+ seed: (seed) => seed === undefined || seed === 'image',
75
76
  retention: (retention) => retention === 'retain' || retention === 'delete-on-destroy',
76
77
  });
77
78
  }
@@ -1 +1 @@
1
- {"version":3,"file":"isInfraWorkloadSpec.js","sourceRoot":"","sources":["../../src/infra/isInfraWorkloadSpec.ts"],"names":[],"mappings":"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","sourcesContent":["import { isStringArray } from '@ankhorage/utility/array';\nimport { isRecord } from '@ankhorage/utility/object';\nimport { isNonEmptyString } from '@ankhorage/utility/string';\n\nimport type { InfraShape } from '../types/infraValidation';\nimport type { InfraWorkloadSpec } from '../types/infraWorkload';\nimport { infraFields } from './infraFields';\nimport { isInfraShape } from './isInfraShape';\nimport { isInfraWorkloadHealth } from './isInfraWorkloadHealth';\nimport { isInfraWorkloadValue } from './isInfraWorkloadValue';\n\n/*** Validate the portable desired workload; runtime-specific fields and plaintext secret objects fail. */\nexport function isInfraWorkloadSpec(value: unknown): value is InfraWorkloadSpec {\n if (\n !isInfraShape(value, {\n id: isNonEmptyString,\n artifact: (artifact) =>\n isInfraShape(artifact, { kind: (kind) => kind === 'image', image: isNonEmptyString }),\n command: infraFields.optionalStrings,\n args: (args) => args === undefined || isStringArray(args),\n ports: (ports) =>\n ports === undefined ||\n (Array.isArray(ports) && ports.every(isPort) && hasUniqueField(ports, 'name')),\n environment: (environment) =>\n environment === undefined ||\n (isRecord(environment) && Object.values(environment).every(isInfraWorkloadValue)),\n files: (files) =>\n files === undefined ||\n (Array.isArray(files) && files.every(isFile) && hasUniqueField(files, 'path')),\n health: (health) => health === undefined || isInfraWorkloadHealth(health),\n resources: (resources) =>\n resources === undefined ||\n isInfraShape(resources, {\n cpuMillis: infraFields.optionalPositiveInteger,\n memoryMiB: infraFields.optionalPositiveInteger,\n }),\n persistence: (volumes) =>\n volumes === undefined ||\n (Array.isArray(volumes) &&\n volumes.every(isVolume) &&\n hasUniqueField(volumes, 'id') &&\n hasUniqueField(volumes, 'mountPath')),\n exposure: (exposure) =>\n exposure === undefined || exposure === 'internal' || exposure === 'public',\n replicas: infraFields.optionalNonnegativeInteger,\n dependsOn: infraFields.optionalStrings,\n } satisfies InfraShape<InfraWorkloadSpec>)\n ) {\n return false;\n }\n return hasValidPublishedPorts(value);\n}\n\n/*** Named transport ports stay independent from runtime resource types. */\nfunction isPort(value: unknown): boolean {\n return isInfraShape(value, {\n name: isNonEmptyString,\n port: infraFields.port,\n protocol: (protocol) => protocol === undefined || protocol === 'tcp' || protocol === 'udp',\n publishedPort: infraFields.optionalPort,\n });\n}\n\n/*** Require fixed external listeners to belong to one public workload replica. */\nfunction hasValidPublishedPorts(workload: unknown): boolean {\n if (!isRecord(workload)) return false;\n const ports = Array.isArray(workload.ports) ? workload.ports : [];\n const published = ports.flatMap((port) => {\n if (!isRecord(port) || typeof port.publishedPort !== 'number') return [];\n return [port.publishedPort];\n });\n return (\n new Set(published).size === published.length &&\n (published.length === 0 ||\n (workload.exposure === 'public' &&\n (workload.replicas === undefined ||\n (typeof workload.replicas === 'number' && workload.replicas <= 1))))\n );\n}\n\n/*** Portable files model policy/config materialization without host filesystem access. */\nfunction isFile(value: unknown): boolean {\n return isInfraShape(value, { path: isAbsoluteWorkloadPath, content: isInfraWorkloadValue });\n}\n\n/*** Persistence has explicit ownership-local identity and a retention policy. */\nfunction isVolume(value: unknown): boolean {\n return isInfraShape(value, {\n id: isNonEmptyString,\n mountPath: isAbsoluteWorkloadPath,\n sizeGiB: infraFields.positiveInteger,\n retention: (retention) => retention === 'retain' || retention === 'delete-on-destroy',\n });\n}\n\n/*** Workload paths are absolute container paths, never relative host traversal. */\nfunction isAbsoluteWorkloadPath(value: unknown): boolean {\n return (\n typeof value === 'string' &&\n value.startsWith('/') &&\n value.length > 1 &&\n !value.split('/').includes('..')\n );\n}\n\n/*** Duplicate workload-local identities would make runtime projection ambiguous. */\nfunction hasUniqueField(values: readonly unknown[], field: string): boolean {\n const ids = values.map((value) => (isRecord(value) ? Reflect.get(value, field) : undefined));\n return new Set(ids).size === ids.length;\n}\n"]}
1
+ {"version":3,"file":"isInfraWorkloadSpec.js","sourceRoot":"","sources":["../../src/infra/isInfraWorkloadSpec.ts"],"names":[],"mappings":"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","sourcesContent":["import { isStringArray } from '@ankhorage/utility/array';\nimport { isRecord } from '@ankhorage/utility/object';\nimport { isNonEmptyString } from '@ankhorage/utility/string';\n\nimport type { InfraShape } from '../types/infraValidation';\nimport type { InfraWorkloadSpec } from '../types/infraWorkload';\nimport { infraFields } from './infraFields';\nimport { isInfraShape } from './isInfraShape';\nimport { isInfraWorkloadHealth } from './isInfraWorkloadHealth';\nimport { isInfraWorkloadValue } from './isInfraWorkloadValue';\n\n/*** Validate the portable desired workload; runtime-specific fields and plaintext secret objects fail. */\nexport function isInfraWorkloadSpec(value: unknown): value is InfraWorkloadSpec {\n if (\n !isInfraShape(value, {\n id: isNonEmptyString,\n artifact: (artifact) =>\n isInfraShape(artifact, { kind: (kind) => kind === 'image', image: isNonEmptyString }),\n command: infraFields.optionalStrings,\n args: (args) => args === undefined || isStringArray(args),\n ports: (ports) =>\n ports === undefined ||\n (Array.isArray(ports) && ports.every(isPort) && hasUniqueField(ports, 'name')),\n environment: (environment) =>\n environment === undefined ||\n (isRecord(environment) && Object.values(environment).every(isInfraWorkloadValue)),\n files: (files) =>\n files === undefined ||\n (Array.isArray(files) && files.every(isFile) && hasUniqueField(files, 'path')),\n health: (health) => health === undefined || isInfraWorkloadHealth(health),\n resources: (resources) =>\n resources === undefined ||\n isInfraShape(resources, {\n cpuMillis: infraFields.optionalPositiveInteger,\n memoryMiB: infraFields.optionalPositiveInteger,\n }),\n persistence: (volumes) =>\n volumes === undefined ||\n (Array.isArray(volumes) &&\n volumes.every(isVolume) &&\n hasUniqueField(volumes, 'id') &&\n hasUniqueField(volumes, 'mountPath')),\n exposure: (exposure) =>\n exposure === undefined || exposure === 'internal' || exposure === 'public',\n replicas: infraFields.optionalNonnegativeInteger,\n dependsOn: infraFields.optionalStrings,\n } satisfies InfraShape<InfraWorkloadSpec>)\n ) {\n return false;\n }\n return hasValidPublishedPorts(value);\n}\n\n/*** Named transport ports stay independent from runtime resource types. */\nfunction isPort(value: unknown): boolean {\n return isInfraShape(value, {\n name: isNonEmptyString,\n port: infraFields.port,\n protocol: (protocol) => protocol === undefined || protocol === 'tcp' || protocol === 'udp',\n publishedPort: infraFields.optionalPort,\n });\n}\n\n/*** Require fixed external listeners to belong to one public workload replica. */\nfunction hasValidPublishedPorts(workload: unknown): boolean {\n if (!isRecord(workload)) return false;\n const ports = Array.isArray(workload.ports) ? workload.ports : [];\n const published = ports.flatMap((port) => {\n if (!isRecord(port) || typeof port.publishedPort !== 'number') return [];\n return [port.publishedPort];\n });\n return (\n new Set(published).size === published.length &&\n (published.length === 0 ||\n (workload.exposure === 'public' &&\n (workload.replicas === undefined ||\n (typeof workload.replicas === 'number' && workload.replicas <= 1))))\n );\n}\n\n/*** Portable files model policy/config materialization without host filesystem access. */\nfunction isFile(value: unknown): boolean {\n return isInfraShape(value, { path: isAbsoluteWorkloadPath, content: isInfraWorkloadValue });\n}\n\n/*** Persistence has explicit ownership-local identity, optional initialization and retention policy. */\nfunction isVolume(value: unknown): boolean {\n return isInfraShape(value, {\n id: isNonEmptyString,\n mountPath: isAbsoluteWorkloadPath,\n sizeGiB: infraFields.positiveInteger,\n seed: (seed) => seed === undefined || seed === 'image',\n retention: (retention) => retention === 'retain' || retention === 'delete-on-destroy',\n });\n}\n\n/*** Workload paths are absolute container paths, never relative host traversal. */\nfunction isAbsoluteWorkloadPath(value: unknown): boolean {\n return (\n typeof value === 'string' &&\n value.startsWith('/') &&\n value.length > 1 &&\n !value.split('/').includes('..')\n );\n}\n\n/*** Duplicate workload-local identities would make runtime projection ambiguous. */\nfunction hasUniqueField(values: readonly unknown[], field: string): boolean {\n const ids = values.map((value) => (isRecord(value) ? Reflect.get(value, field) : undefined));\n return new Set(ids).size === ids.length;\n}\n"]}
@@ -54,6 +54,8 @@ export interface InfraWorkloadVolumeSpec {
54
54
  readonly id: string;
55
55
  readonly mountPath: string;
56
56
  readonly sizeGiB: number;
57
+ /** Seed a newly created empty volume from the image contents at mountPath before workload start. */
58
+ readonly seed?: 'image';
57
59
  /** Persistence survives down and is retained by default on destroy. */
58
60
  readonly retention: 'retain' | 'delete-on-destroy';
59
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"infraWorkload.d.ts","sourceRoot":"","sources":["../../src/types/infraWorkload.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"infraWorkload.d.ts","sourceRoot":"","sources":["../../src/types/infraWorkload.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"infraWorkload.js","sourceRoot":"","sources":["../../src/types/infraWorkload.ts"],"names":[],"mappings":"","sourcesContent":["import type { InfraControlPlaneCredentialRef, InfraSecretReference } from './infraSecrets';\n\nexport interface InfraWorkloadArtifact {\n readonly kind: 'image';\n /** Prebuilt image, optionally pinned by digest. No registry/build provider requirement. */\n readonly image: string;\n}\n\nexport type InfraWorkloadScalarValue =\n | { readonly kind: 'literal'; readonly value: string }\n | { readonly kind: 'secret'; readonly reference: InfraSecretReference }\n | {\n readonly kind: 'credential';\n readonly reference: InfraControlPlaneCredentialRef;\n readonly key: string;\n }\n | { readonly kind: 'output'; readonly resourceId: string; readonly output: string };\n\nexport type InfraWorkloadValue =\n | InfraWorkloadScalarValue\n | {\n readonly kind: 'template';\n /** Ordered scalar segments materialized into one value only at the runtime boundary. */\n readonly segments: readonly InfraWorkloadScalarValue[];\n };\n\nexport interface InfraWorkloadPort {\n readonly name: string;\n readonly port: number;\n readonly protocol?: 'tcp' | 'udp';\n /** Exact external listener port requested for a public single-replica workload. */\n readonly publishedPort?: number;\n}\n\nexport type InfraWorkloadHealthSpec = {\n readonly intervalSeconds?: number;\n readonly timeoutSeconds?: number;\n readonly failureThreshold?: number;\n} & (\n | { readonly kind: 'http'; readonly port: number; readonly path: string }\n | { readonly kind: 'tcp'; readonly port: number }\n | { readonly kind: 'command'; readonly command: readonly string[] }\n);\n\nexport interface InfraWorkloadResourceSpec {\n readonly cpuMillis?: number;\n readonly memoryMiB?: number;\n}\n\nexport interface InfraWorkloadVolumeSpec {\n readonly id: string;\n readonly mountPath: string;\n readonly sizeGiB: number;\n /** Persistence survives down and is retained by default on destroy. */\n readonly retention: 'retain' | 'delete-on-destroy';\n}\n\n/** Portable config/policy files; secret values are materialized by the runtime. */\nexport interface InfraWorkloadFileSpec {\n readonly path: string;\n readonly content: InfraWorkloadValue;\n}\n\nexport interface InfraWorkloadSpec {\n readonly id: string;\n readonly artifact: InfraWorkloadArtifact;\n readonly command?: readonly string[];\n readonly args?: readonly string[];\n readonly ports?: readonly InfraWorkloadPort[];\n readonly environment?: Readonly<Record<string, InfraWorkloadValue>>;\n readonly files?: readonly InfraWorkloadFileSpec[];\n readonly health?: InfraWorkloadHealthSpec;\n readonly resources?: InfraWorkloadResourceSpec;\n readonly persistence?: readonly InfraWorkloadVolumeSpec[];\n readonly exposure?: 'internal' | 'public';\n readonly replicas?: number;\n /** IDs in the composed desired-state graph; the orchestrator validates missing/cyclic edges. */\n readonly dependsOn?: readonly string[];\n}\n"]}
1
+ {"version":3,"file":"infraWorkload.js","sourceRoot":"","sources":["../../src/types/infraWorkload.ts"],"names":[],"mappings":"","sourcesContent":["import type { InfraControlPlaneCredentialRef, InfraSecretReference } from './infraSecrets';\n\nexport interface InfraWorkloadArtifact {\n readonly kind: 'image';\n /** Prebuilt image, optionally pinned by digest. No registry/build provider requirement. */\n readonly image: string;\n}\n\nexport type InfraWorkloadScalarValue =\n | { readonly kind: 'literal'; readonly value: string }\n | { readonly kind: 'secret'; readonly reference: InfraSecretReference }\n | {\n readonly kind: 'credential';\n readonly reference: InfraControlPlaneCredentialRef;\n readonly key: string;\n }\n | { readonly kind: 'output'; readonly resourceId: string; readonly output: string };\n\nexport type InfraWorkloadValue =\n | InfraWorkloadScalarValue\n | {\n readonly kind: 'template';\n /** Ordered scalar segments materialized into one value only at the runtime boundary. */\n readonly segments: readonly InfraWorkloadScalarValue[];\n };\n\nexport interface InfraWorkloadPort {\n readonly name: string;\n readonly port: number;\n readonly protocol?: 'tcp' | 'udp';\n /** Exact external listener port requested for a public single-replica workload. */\n readonly publishedPort?: number;\n}\n\nexport type InfraWorkloadHealthSpec = {\n readonly intervalSeconds?: number;\n readonly timeoutSeconds?: number;\n readonly failureThreshold?: number;\n} & (\n | { readonly kind: 'http'; readonly port: number; readonly path: string }\n | { readonly kind: 'tcp'; readonly port: number }\n | { readonly kind: 'command'; readonly command: readonly string[] }\n);\n\nexport interface InfraWorkloadResourceSpec {\n readonly cpuMillis?: number;\n readonly memoryMiB?: number;\n}\n\nexport interface InfraWorkloadVolumeSpec {\n readonly id: string;\n readonly mountPath: string;\n readonly sizeGiB: number;\n /** Seed a newly created empty volume from the image contents at mountPath before workload start. */\n readonly seed?: 'image';\n /** Persistence survives down and is retained by default on destroy. */\n readonly retention: 'retain' | 'delete-on-destroy';\n}\n\n/** Portable config/policy files; secret values are materialized by the runtime. */\nexport interface InfraWorkloadFileSpec {\n readonly path: string;\n readonly content: InfraWorkloadValue;\n}\n\nexport interface InfraWorkloadSpec {\n readonly id: string;\n readonly artifact: InfraWorkloadArtifact;\n readonly command?: readonly string[];\n readonly args?: readonly string[];\n readonly ports?: readonly InfraWorkloadPort[];\n readonly environment?: Readonly<Record<string, InfraWorkloadValue>>;\n readonly files?: readonly InfraWorkloadFileSpec[];\n readonly health?: InfraWorkloadHealthSpec;\n readonly resources?: InfraWorkloadResourceSpec;\n readonly persistence?: readonly InfraWorkloadVolumeSpec[];\n readonly exposure?: 'internal' | 'public';\n readonly replicas?: number;\n /** IDs in the composed desired-state graph; the orchestrator validates missing/cyclic edges. */\n readonly dependsOn?: readonly string[];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/contracts",
3
- "version": "19.3.0",
3
+ "version": "19.4.0",
4
4
  "main": "./dist/index.js",
5
5
  "ankh": {
6
6
  "category": "contracts",
@@ -83,12 +83,13 @@ function isFile(value: unknown): boolean {
83
83
  return isInfraShape(value, { path: isAbsoluteWorkloadPath, content: isInfraWorkloadValue });
84
84
  }
85
85
 
86
- /*** Persistence has explicit ownership-local identity and a retention policy. */
86
+ /*** Persistence has explicit ownership-local identity, optional initialization and retention policy. */
87
87
  function isVolume(value: unknown): boolean {
88
88
  return isInfraShape(value, {
89
89
  id: isNonEmptyString,
90
90
  mountPath: isAbsoluteWorkloadPath,
91
91
  sizeGiB: infraFields.positiveInteger,
92
+ seed: (seed) => seed === undefined || seed === 'image',
92
93
  retention: (retention) => retention === 'retain' || retention === 'delete-on-destroy',
93
94
  });
94
95
  }
@@ -0,0 +1,30 @@
1
+ import { expect, it } from 'bun:test';
2
+
3
+ import { isInfraWorkloadSpec } from './infra';
4
+
5
+ const workload = {
6
+ id: 'database',
7
+ artifact: { kind: 'image', image: 'example/database:1' },
8
+ persistence: [
9
+ {
10
+ id: 'config',
11
+ mountPath: '/etc/database-custom',
12
+ sizeGiB: 1,
13
+ seed: 'image',
14
+ retention: 'retain',
15
+ },
16
+ ],
17
+ } as const;
18
+
19
+ it('accepts image-seeded persistence initialization', () => {
20
+ expect(isInfraWorkloadSpec(workload)).toBe(true);
21
+ });
22
+
23
+ it('rejects unsupported persistence initialization modes', () => {
24
+ expect(
25
+ isInfraWorkloadSpec({
26
+ ...workload,
27
+ persistence: [{ ...workload.persistence[0], seed: 'empty' }],
28
+ }),
29
+ ).toBe(false);
30
+ });
package/src/infra.test.ts CHANGED
@@ -140,39 +140,6 @@ describe('runtime topology configuration', () => {
140
140
  });
141
141
  });
142
142
 
143
- describe('sibling service configuration', () => {
144
- it('accepts Supabase auth, Cerbos and independent R2 storage', () => {
145
- expect(isInfraEnvironmentSpec(production)).toBe(true);
146
- expect(isInfraEnvironmentSpec({ ...production, objectStorage: { provider: 'supabase' } })).toBe(
147
- true,
148
- );
149
- expect(isInfraEnvironmentSpec({ ...local, auth: { provider: 'supabase' } })).toBe(true);
150
- });
151
-
152
- it.each([
153
- { database: { provider: 'postgres' } },
154
- { database: { provider: 'supabase', tier: 'enterprise' } },
155
- { objectStorage: { provider: 'auto' } },
156
- { objectStorage: { provider: 's3' } },
157
- { objectStorage: { provider: 'supabase', buckets: [1] } },
158
- { objectStorage: { provider: 'supabase', accountId: 'r2-account' } },
159
- { auth: { provider: 'custom' } },
160
- { auth: { provider: 'supabase', scope: 'unknown' } },
161
- { auth: { provider: 'supabase', authorization: { kind: 'ABAC', engine: 'cerbos' } } },
162
- { authz: { provider: 'native', kind: 'RBAC' } },
163
- { authz: { engine: 'cerbos', kind: 'ABAC' } },
164
- { authz: { provider: 'cerbos', kind: 'unknown' } },
165
- { secretStore: { provider: 'unknown' } },
166
- { secretStore: { provider: 'supabase-vault' } },
167
- { networking: { domain: 'example.ch', cdn: true } },
168
- { networking: { publicBaseUrl: 'ftp://api.example.ch' } },
169
- { networking: { publicBaseUrl: 'https://api.example.ch/auth/v1' } },
170
- { networking: { publicBaseUrl: 'https://api.example.ch/' } },
171
- ])('rejects unsupported service selection: %j', (selection) => {
172
- expect(isInfraEnvironmentSpec({ ...local, ...selection })).toBe(false);
173
- });
174
- });
175
-
176
143
  describe('Cerbos policy configuration', () => {
177
144
  it('accepts portable policy files', () => {
178
145
  expect(
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { type InfraEnvironmentSpec, isInfraEnvironmentSpec } from './infra';
4
+
5
+ const local = {
6
+ deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
7
+ } as const satisfies InfraEnvironmentSpec;
8
+ const production = {
9
+ deployment: {
10
+ compute: { provider: 'hetzner', location: 'nbg1' },
11
+ runtime: { provider: 'k3s', topology: { servers: 1, agents: 0 } },
12
+ },
13
+ database: { provider: 'supabase' },
14
+ auth: { provider: 'supabase' },
15
+ objectStorage: { provider: 'r2', buckets: ['media'] },
16
+ authz: { provider: 'cerbos', kind: 'ABAC' },
17
+ secretStore: { provider: 'supabase-vault' },
18
+ networking: { domain: 'api.example.ch', publicBaseUrl: 'https://api.example.ch' },
19
+ } as const satisfies InfraEnvironmentSpec;
20
+
21
+ const invalidServiceSelections: Record<string, unknown>[] = [
22
+ { database: { provider: 'postgres' } },
23
+ { database: { provider: 'supabase', tier: 'enterprise' } },
24
+ { objectStorage: { provider: 'auto' } },
25
+ { objectStorage: { provider: 's3' } },
26
+ { objectStorage: { provider: 'supabase', buckets: [1] } },
27
+ { objectStorage: { provider: 'supabase', accountId: 'r2-account' } },
28
+ { auth: { provider: 'custom' } },
29
+ { auth: { provider: 'supabase', scope: 'unknown' } },
30
+ { auth: { provider: 'supabase', authorization: { kind: 'ABAC', engine: 'cerbos' } } },
31
+ { authz: { provider: 'native', kind: 'RBAC' } },
32
+ { authz: { engine: 'cerbos', kind: 'ABAC' } },
33
+ { authz: { provider: 'cerbos', kind: 'unknown' } },
34
+ { secretStore: { provider: 'unknown' } },
35
+ { secretStore: { provider: 'supabase-vault' } },
36
+ ];
37
+
38
+ describe('sibling service configuration', () => {
39
+ it('accepts Supabase auth, Cerbos and independent R2 storage', () => {
40
+ expect(isInfraEnvironmentSpec(production)).toBe(true);
41
+ expect(isInfraEnvironmentSpec({ ...production, objectStorage: { provider: 'supabase' } })).toBe(
42
+ true,
43
+ );
44
+ expect(isInfraEnvironmentSpec({ ...local, auth: { provider: 'supabase' } })).toBe(true);
45
+ });
46
+
47
+ it.each(invalidServiceSelections)('rejects unsupported service selection: %j', (selection) => {
48
+ expect(isInfraEnvironmentSpec({ ...local, ...selection })).toBe(false);
49
+ });
50
+ });
@@ -51,6 +51,8 @@ export interface InfraWorkloadVolumeSpec {
51
51
  readonly id: string;
52
52
  readonly mountPath: string;
53
53
  readonly sizeGiB: number;
54
+ /** Seed a newly created empty volume from the image contents at mountPath before workload start. */
55
+ readonly seed?: 'image';
54
56
  /** Persistence survives down and is retained by default on destroy. */
55
57
  readonly retention: 'retain' | 'delete-on-destroy';
56
58
  }